Introduction
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 and Grafana.
Getting Started
Getting started with Timely requires that you:
Use the standalone server to get a test environment up and running quickly
Install and configure a Timely server
Install and configure the Timely app within Grafana
Send metrics to Timely using CollectD, OpenTSDBs TCollector, etc.
Timely should accept data from TCollector with no changes. A plugin exists for sending data from CollectD.
Timely Server
Starting Timely
Note: 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.
If you are just starting out with Timely and want to see what it can do, then start up the standalone server using the bin/timely-standalone.sh
script. This will start up the necessary HDFS, Accumulo, and Timely processes on your local machine. You can use the bin/insert-test-data*.sh
scripts to push same fake data to the Timely server.
To deploy Timely with a running Accumulo instance you will need to modify the conf/timely.yml
file appropriately. Then copy the lib/timely-client.jar
, lib/timely-server.jar
, lib/commons-lang3-*.jar
, lib/commons-collections4-*.jar
, and lib/guava-*.jar
files to your Accumulo tablet servers. Finally, launch Timely using the bin/timely-server.sh
script. Because Timely uses a newer version of Guava, you will need to configure a classloader for the Timely context, set it to use post-delegation, and then configure your tables to use the Timely context classloader. This can be done in the shell or in the accumulo-site.xml file.
In accumulo shell:
general.vfs.context.classpath.timely=<some location>
general.vfs.context.classpath.timely.delegation=post
SSL Setup
To user Timely, both Timely and Grafana will need to use SSL certificates. Grafana requires a PEM encoded certificate file and an un-encrypted PEM private key file. Timely requires a PEM encoded certificate file and a PKCS#8 encoded private key file. Timely supports private keys with and without a password, just comment out the timely.security.ssl.key-password
property if there is no password for your private key. In a successful setup, you should only need to add the Timely Certificate Authority to your web browser.
Creating your own SSL keys and certificates
There are plenty of resources on the Internet for doing this. This example is taken from instructions in the Accumulo user manual.
Create your Certificate Authority
Create a private key
openssl genrsa -aes256 -out CA.key 4096
Create a certificate request using the private key
openssl req -x509 -new -key CA.key -sha256 -subj '/C=US/ST=Confusion/L=Here/O=Timely/OU=CA/CN=localhost/emailAddress=noreply@localhost/subjectAltName=DNS.1=127.0.0.1' -nodes -days 365 -out CA.pem
Create your SSL material for Grafana
Create the private key for the Grafana server
openssl genrsa -out grafana.key 4096
Generate a certificate signing request (CSR) with our Grafana private key
openssl req -new -key grafana.key -sha256 -subj '/C=US/ST=Confusion/L=Here/O=Grafana/OU=Server/CN=localhost/emailAddress=noreply@localhost/subjectAltName=DNS.1=127.0.0.1' -nodes -out grafana.csr
Use the CSR and the CA to create a certificate for the server (a reply to the CSR)
openssl x509 -req -in grafana.csr -CA CA.pem -CAkey CA.key -CAcreateserial -out grafana.crt -days 365
Create your SSL material for Timely
Create the private key for the Timely server
openssl genrsa -out timely.key 4096
Generate a certificate signing request (CSR) with our Timely private key
openssl req -new -key timely.key -sha256 -nodes -subj '/C=US/ST=Confusion/L=Here/O=Timely/OU=Server/CN=localhost/emailAddress=noreply@localhost/subjectAltName=DNS.1=127.0.0.1' > timely.csr
Use the CSR and the CA to create a certificate for the server (a reply to the CSR)
openssl x509 -req -in timely.csr -CA CA.pem -CAkey CA.key -CAcreateserial -out timely.crt -days 365
Convert the private key to pkcs#8 format
openssl pkcs8 -topk8 -inform PEM -outform PEM -in timely.key -out timely-pkcs8.key
Configuration
timely:
accumulo:
instance-name: MyInstanceName
The default configuration file type for Timely is a YAML. The configuration is read internally using Spring Boot, and therefore supports all of the configuration styles supported by Spring Boot. See here and here for more details. In YAML, a property such as timely.accumulo.instance-name=MyInstanceName (which can also be timely.accumulo.instanceName) would look like the example on the right.
The NUM_SERVER_THREADS
variable in the timely-server.sh
script controls how many threads are used in the Netty event group for UPD, TCP, HTTP, and WebSocket operations. The different protocols each use a Netty event group, so if you set the value to 8, then you will have 4 thread groups of 8 threads each. The properties file in the conf
directory supports the following properties:
Note: Each thread in the Timely server that is used for processing TCP put operations has its own BatchWriter. Each BatchWriter honors the
timely.accumulo.write.latency
andtimely.accumulo.write.threads
configuration property, but the buffer size for each BatchWriter istimely.accumulo.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: timely.accumulo.write.latency=30s, timely.accumulo.write.threads=2 timely.accumulo.write.buffer-size=1GNote: The
timely.accumulo.scan.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.Note: 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 thetimely.meta-cache.*
properties.
Property | Description | Default Value |
---|---|---|
timely.metrics-table | The name of the metrics table | timely.metrics |
timely.meta-table | The name of the meta table | timely.meta |
timely.metric-age-off-days | A list of metric name prefix to age-off day pairs. The default prefix applies to every key that isn’t matched by another ageoff rule. |
default: 7 |
timely.metrics-report-ignored-tags | Comma separated list of tags which will not be shown in the /api/metrics response | |
timely.accumulo.instance-name | The name of your Accumulo instance | |
timely.accumulo.zookeepers | The list of Zookeepers for your Accumulo instance | |
timely.accumulo.username | The username that Timely will use to connect to Accumulo | |
timely.accumulo.password | The password of the Accumulo user | |
timely.accumulo.write.latency | The Accumulo BatchWriter latency | 5s |
timely.accumulo.write.threads | The Accumulo BatchWriter number of threads | default |
timely.accumulo.write.buffer-size | The Accumulo BatchWriter buffer size | default |
timely.accumulo.scan.threads | Number of BatchScanner threads to be used in a query | 4 |
timely.security.session-max-age | Setting for max age of session cookie (in seconds) | 86400 |
timely.security.allow-anonymous-access | Allow anonymous access | false |
timely.security.ssl.use-generated-keypair | Use a generated certificate/key pair - useful for testing | false |
timely.security.ssl.certificate.file | Public certificate to use for the Timely server (x509 pem format) | |
timely.security.ssl.key-file | Private key to use for the Timely server (in pkcs8 format) | |
timely.security.ssl.key-password | Password to the private key | |
timely.security.ssl.trust-store-file | Certificate trust store (a concatenated list of trusted CA x509 pem certificates) | |
timely.security.ssl.use-openssl | Use OpenSSL (vs JDK SSL) | true |
timely.security.ssl.use-ciphers | List of allowed SSL ciphers | see Configuration.java |
timely.server.ip | The IP address where the Timely server is running | |
timely.server.tcp-port | The port that will be used for processing TCP put requests | |
timely.server.udp-port | The port that will be used for processing UDP put requests | |
timely.server.shutdown-quiet-period | Time to wait (in seconds) during shutdown for existing connections to finish and ensure there are no new connections | 5 |
timely.http.host | Address for the Timely server, used for the session cookie domain | |
timely.http.ip | The IP address where the Timely server is listening for HTTP query requests | |
timely.http.port | The port that will be used for processing query requests | |
timely.http.redirect-path | Path to use for HTTP to HTTPS redirect | /secure-me |
timely.http.strict-transport-max-age | HTTP Strict Transport Security max age (in seconds) | 604800 |
timely.http.cors.allow-any-origin | Allow any origin in cross origin requests (true/false) | false |
timely.http.cors.allow-null-origin | Allow null origins in cross origin requests (true/false) | false |
timely.http.cors.allowed-origins | List of allowed origins in cross origin requests (can be null or comma separated list) | |
timely.http.cors.allowed-methods | List of allowed methods for cross origin requests |
|
timely.http.cors.allowed-headers | Comma separated list of allowed HTTP headers for cross origin requests | content-type |
timely.http.cors.allow-credentials | Allow credentials to be passed in cross origin requests (true/false) | true |
timely.websocket.ip | The IP address where the Timely server is listening for web socket requests | |
timely.websocket.port | The port that will be used for processing web socket requests | |
timely.websocket.timeout | Number of seconds with no client ping response before closing subscription | 60 |
timely.websocket.subscription-lag | Number of seconds that subscriptions should lag to account for latency | 120 |
timely.websocket.scanner-batch-size | Accumulo Scanner batch size | 5000 |
timely.websocket.flush-interval-seconds | Time in seconds that Timely will send a batch of subscription responses to the client | 30 |
timely.websocket.scanner-read-ahead | Accumulo Scanner Readahead threshold | 1 |
timely.websocket.subscription-batch-size | Maximum number of metric responses in a batch sent back to the client | 1000 |
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.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 |
Data Storage
Metrics sent to Timely are stored in two Accumulo tables, meta
and metrics
.
Meta Table Format
Row | ColumnFamily | ColumnQualifier | Value |
---|---|---|---|
m:metric | |||
t:metric | tagKey | ||
v:metric | tagKey | tagValue |
Metric Table Format
Row | ColumnFamily | ColumnQualifier | Value |
---|---|---|---|
metric\timestamp | tagKey=tagValue | tagKey=tagValue,tagKey=tagValue,… | metricValue |
Data Storage Example
As an example, if you sent the following metric to the put api: sys.cpu.user 1447879348291 2.0 rack=r001 host=r001n01 instance=0
it would get stored in the following manner in the meta
table:
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 |
and in the following manner in the metrics
table
Note: Not shown in the examples is the encoding of the row. 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
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 |
Accumulo Configuration
You can generate split points for the metrics table after sending data to Timely for a short amount of time. The bin/get-metric-split-points.sh
script will print out a set of split points based on the metric names in the meta table. You can use this output in the Accumulo addsplits
command.
You can lower the table.scan.max.memory
property on your metrics table. This will send data back from the Tablet Servers to the Timely server at a faster rate.
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.
Security
Securing the Transport
Timely only exposes two operations over a non-secure transport, one to get the version of the server and the other to insert data. All other operations are protected by either HTTPS or WSS protocols. The server configuration allows the user to change many of the security settings such as: CORS origins, specific SSL/TLS ciphers, duration of the HTTP session cookie, and more.
HTTP Strict Transport Security
Timely returns a HTTP Strict Transport Security header to callers that attempt to use HTTP. The max age for this header can be set in the configuration.
Securing Data
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 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. Additionally, column visibilities are not returned with the data.
With anonymous access users that have not logged in will only 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. When anonymous access is disabled, a call to any operation that requires the session id will fail. When anonymous access is enabled, these calls will succeed but only unlabeled data will be returned. When using Grafana, it must be configured to use https also. For more information see the Quick Start documentation.
Standalone Quick Start
- Build Timely
- set
JAVA_HOME
to point to a JDK 8 installation - run
mvn clean package
from the top level directory in the source tree
- set
- Install latest version of Grafana that works with Timely (4.4.3)
- Create a grafana configuration file (e.g:
conf/settings.ini
)protocol = https
http_addr = localhost
cert_file= <path to PEM encoded certificate file>
key_file= <path to PEM encoded un-encrypted private key file>
- Untar the timely-app distribution tarball located in grafana/target in the grafana plugins directory (default: /var/lib/grafana/plugins)
- Start Grafana (
./bin/grafana-server -c conf/settings.ini
)- Visit
https://localhost:3000
to verify Grafana is working
- Visit
- Untar Timely server distribution (found in
target/timely-server-(VERSION)-SNAPSHOT-dist.tar.gz
) - Modify
timely-standalone.yml
:- Use generated server side SSL certificates
timely.security.ssl.use-generated-keypair=true
- or, use your own SSL certificates - see SSL Setup
timely.security.ssl.use-generated-keypair=false
timely.security.ssl.certificate-file=<path to PEM encoded certificate file>
timely.security.ssl.key-file=<path to PKCS8 PEM encoded private key file>
timely.security.ssl.key-password=<private key password>
timely.security.ssl.use-openssl=true
(if openssl installed locally, else false)timely.security.ssl.use-ciphers=<list of ciphers>
(comma-delimited list of ciphers to use if you do not want to use the default)- Set Timely domain information, this is used for the HTTP Cookie
timely.http.host=localhost
- Set Grafana login address, used for HTTP redirect after login
grafana.http.address=https://localhost:3000/login
- Set Anonymous access for Timely
timely.security.allow-anonymous-access=<true or false>
- Start the Timely standalone server
cd bin; ./timely-standalone.sh
- Insert test data
cd bin; ./insert-test-data.sh
- Add the Timely datasource to Grafana
- Login to Grafana, go to ‘DataSources’
- Click 'Add data source’
- Enter the following information:
- Name:
Timely Standalone
- Type:
Timely
- Hostname: localhost
- HTTPS Port: 54322
- WS Port: 54323
- Basic Auths: check
- Click
Browser Cert Check
and accept the certificate - Click
Save & Test
.
- Import Standalone Test dashboard into Grafana.
Notes on Security Options
- If anonymous access is disabled, then users will only be able to see unlabeled data.
- If anonymous access is enabled, then users must login before Grafana will work.
- Access control is configured in
conf/security.xml
and supports basic auth and SSL client certificates. Upon a successful login the response will include a HTTP Session Cookie. Once this is set, access to Timely from Grafana should work.- For SSL client certificate auth, users should perform a GET request to the
/login
endpoint over HTTPS. The defaultconf/security.xml
specifies a userexample.com
with authorizationsD
,E
,F
. The username will be extracted from the certificate’sCN
using thex509PrincipalExtractor
bean. - For basic password authentication users should perform a POST request to the
/login
endpoint. - This can be done using Poster or HttpRequester with the content-type of “application/json” and the following content:
{ "username": "<>", "password": "<>" }
- The default
conf/security.xml
specifies a usertest
with passwordtest1
that has the authorizations forA
,B
,C
.
- For SSL client certificate auth, users should perform a GET request to the
- The user specified in
timely.accumulo.username
must have authorizations compatible with the visibility values on your data to be able to return that data to a client. Usesetauths
in the Accumulo shell to configure this. insert-test-data.sh
will insert some data with visibilities, specificallysys.eth0.rx.*
will haveA
,B
orC
andsys.eth0.tx.*
will haveD
,E
orF
.
Grafana
The grafana directory in the source tree contains the Timely application for Grafana.
Compiling
For development, running
grunt watch
will watch for file changes and continuously rebuild everything.
Compiling the timely-app requrires NodeJS to be installed. Once installed, simply download
the dependencies with npm
and run grunt
cd grafana/timely-app
npm install
grunt
Note: for the grunt
command to work, you must have the grunt-cli application installed on your system: npm install -g grunt-cli
Packaging
Run mvn clean package
Deployment
Take the resulting tar and unpack it in /var/lib/grafana/plugins
on your Grafana server and restart Grafana.
Using the Timely App
Login to Grafana and the Home dashboard should show the Timely App is available.
Enable Timely App
Once installed, the Timely App must be enabled in Grafana. Clicking the Enable Now link on the Home Dashboard or navigate to the app list in the Grafana Plugins page.
These will both bring you to the enable page.
Once enabled, Timely should appear in Grafana menu.
Create Datasource
Go to the Grafana Data Sources menu and add a new Data Source. In the Type drop down menu, select Timely and the Timely Server settings UI will populate.
Fill in the timely server details as required. The Browser Test opens a new browser window to allow you to add an exception to the Timely server cert. This is only necessary if using self-signed certs.
Click Add, and the datasource will be tested and added.
Clicking the Dashboards tab will allow you to import a dashboard to display Timely’s internal metrics. Depending on the security setup, the dashboard may not show any data until the Login process is completed.
Login
From the Timely App menu, select the Login page. This will present you a login prompt to authenticate you with the Timely datasources. This will set a cookie in your Browser that will be passed with the dashboard queries and Timely will pass your Accumulo column visibilities during data scans.
View dashboard - this should now be working
If more than one Timely datasource is defined, multiple Login prompts will be available. Here, the top data source was defined with BasicAuths checked, and the second one assumes a valid Certificate is loaded in the browser and will be passed to Timely when Login is clicked.
Metrics Browser
To help browsing the data in Timely, the Timely menu also contains a Metrics link to take you to the Metrics Broswer
Timely data sources can be selected from the drop down menu and the results can be filtered by typing in the filter box.
Dashboard
Currently the imported dashboard has one plot for timely ingest metrics, and three other plots to display the artificial data inserted by the timely.util.InsertTestData
program.
Plugins for CollectD
The source tree includes plugins for CollectD, an agent based system statistics collector. The CollectD plugins parse and transform the metrics and sends them to Timely.
CollectD plugin to send data to Timely
LoadPlugin java
<Plugin java>
JVMArg "-verbose:jni"
JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar:<path_to_jar>/collectd-timely-plugin.jar"
JVMArg "-Xms128m"
JVMArg "-Xmx128m"
LoadPlugin "timely.collectd.plugin.WriteTimelyPlugin"
<Plugin "timely.collectd.plugin.WriteTimelyPlugin">
Host "<TimelyHost>"
Port "<TimelyPutPort>"
Tags "<comma separated list of additional key=value pairs>"
</Plugin>
</Plugin>
To build, run mvn clean package
in the collectd-timely-plugin directory. Place the resulting jar file somewhere on the local filesystem and the plugin configuration to the right to the collectd configuration file.
CollectD plugin to send data to NSQ
LoadPlugin java
<Plugin java>
JVMArg "-verbose:jni"
JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar:<path_to_jar>/collectd-nsq-plugin.jar"
JVMArg "-Xms128m"
JVMArg "-Xmx128m"
LoadPlugin "timely.collectd.plugin.WriteNSQPlugin"
<Plugin "timely.collectd.plugin.WriteNSQPlugin">
Host "<NSQHost[,NSQHost]>"
Port "<NSQHttpPort>"
Tags "<comma separated list of additional key=value pairs>"
</Plugin>
</Plugin>
To build, run mvn clean package
in the collectd-nsq-plugin directory. Place the resulting jar file somewhere on the local filesystem and add the plugin configuration to the right to the collectd configuration file.
Collecting Metrics from Hadoop and Accumulo
CollectD contains StatsD plugin that listens on a configured port for UDP traffic in the StatsD protocol. More recent versions of Hadoop contain a StatsD sink for the Hadoop Metrics2 framework. Accumulo also uses the Hadoop Metrics2 framework and when configured correctly can emit its metrics via the same mechanism.
Configuring Hadoop to use the StatsD sink
*.sink.statsd.class=org.apache.hadoop.metrics2.sink.StatsDSink
*.sink.statsd.period=60
namenode.sink.statsd.server.host=127.0.0.1
namenode.sink.statsd.server.port=8125
namenode.sink.statsd.skip.hostname=true
namenode.sink.statsd.service.name=NameNode
datanode.sink.statsd.server.host=127.0.0.1
datanode.sink.statsd.server.port=8125
datanode.sink.statsd.skip.hostname=true
datanode.sink.statsd.service.name=DataNode
resourcemanager.sink.statsd.server.host=127.0.0.1
resourcemanager.sink.statsd.server.port=8125
resourcemanager.sink.statsd.skip.hostname=true
resourcemanager.sink.statsd.service.name=ResourceManager
nodemanager.sink.statsd.server.host=127.0.0.1
nodemanager.sink.statsd.server.port=8125
nodemanager.sink.statsd.skip.hostname=true
nodemanager.sink.statsd.service.name=NodeManager
mrappmaster.sink.statsd.server.host=127.0.0.1
mrappmaster.sink.statsd.server.port=8125
mrappmaster.sink.statsd.skip.hostname=true
mrappmaster.sink.statsd.service.name=MRAppMaster
jobhistoryserver.sink.statsd.server.host=127.0.0.1
jobhistoryserver.sink.statsd.server.port=8125
jobhistoryserver.sink.statsd.skip.hostname=true
jobhistoryserver.sink.statsd.service.name=JobHistoryServer
maptask.sink.statsd.server.host=127.0.0.1
maptask.sink.statsd.server.port=8125
maptask.sink.statsd.skip.hostname=true
maptask.sink.statsd.service.name=MapTask
reducetask.sink.statsd.server.host=127.0.0.1
reducetask.sink.statsd.server.port=8125
reducetask.sink.statsd.skip.hostname=true
reducetask.sink.statsd.service.name=ReduceTask
Uncomment and configure the *.period
property, then append the content to the right to the hadoop-metrics2.properties file.
Configuring Accumulo to use the StatsD sink
accumulo.sink.statsd-tserver.class=org.apache.hadoop.metrics2.sink.StatsDSink
accumulo.sink.statsd-tserver.server.host=127.0.0.1
accumulo.sink.statsd-tserver.server.port=8125
accumulo.sink.statsd-tserver.skip.hostname=true
accumulo.sink.statsd-tserver.service.name=TabletServer
accumulo.sink.statsd-master.class=org.apache.hadoop.metrics2.sink.StatsDSink
accumulo.sink.statsd-master.server.host=127.0.0.1
accumulo.sink.statsd-master.server.port=8125
accumulo.sink.statsd-master.skip.hostname=true
accumulo.sink.statsd-master.service.name=Master
accumulo.sink.statsd-thrift.class=org.apache.hadoop.metrics2.sink.StatsDSink
accumulo.sink.statsd-thrift.server.host=127.0.0.1
accumulo.sink.statsd-thrift.server.port=8125
accumulo.sink.statsd-thrift.skip.hostname=true
accumulo.sink.statsd-thrift.service.name=Thrift
Uncomment and configure the *.period
property, then append the content to the right to the hadoop-metrics2-accumulo.properties file.
API Overview
The Timely server supports clients sending requests over different protocols. Not all operations are supported over all protocols. The table below summarizes the protocols supported for each operation. The API documention is organized into sections that describe the general, time series, and subscription operations.
The TCP, UDP, HTTPS, and WebSocket ports can be specified in the Timely configuration properties.
The endpoint for the WebSocket protocol is
/websocket
Operation | UDP | TCP | HTTPS | WebSocket |
---|---|---|---|---|
Version | X | X | X | |
Login | X | |||
Put | X | X | X | X |
Suggest | X | X | ||
Lookup | X | X | ||
Query | X | X | ||
Aggregators | X | X | ||
Metrics | X | X | ||
Create | X | |||
Add | X | |||
Remove | X | |||
Close | X |
Timely Client
Java Timely client library was started in 0.0.4, it is not complete, but what is done does work. The client library includes classes for communicating over all the protocols (UDP, TCP, HTTPS, WSS).
General API
Version Operation
echo "version" | nc <host> <port>
GET /version HTTP/1.1
POST /version HTTP/1.1
{
"operation" : "version"
}
Get the version of the Timely server. Returns a Version response.
Version Response
0.0.2
HTTP/1.1 200 OK
0.0.2
0.0.2
Represents the version of the Timely server. Contents are a single string with the version.
Login Operation
GET /login HTTP/1.1
POST /login HTTP/1.1
{
"username" : "user",
"password" : "pass"
}
Timely uses Spring Security for user authentication. When successful, a HTTP Set-Cookie header is returned in the response with the name TSESSIONID
. If a user does not log in, and anonymous access is enabled, then the user will only see data that is not marked. If a user does not log in, and anonymous access is disabled, then any operation that requires authentication will return an error. HTTP GET and POST methods are supported, the GET request is used when client SSL authentication is configured. When using the HTTP protocol, the TSESSIONID
cookie should be sent along with the request. For the WebSocket protocol the client will need to add the TSESSIONID
cookie value to the request in the sessionId
property. If using the WebSocket protocol with anonymous access, then use a unique sessionId
value for the duration of the client session. Returns a Login response.
Login Response
HTTP/1.1 200 OK
Set-Cookie TSESSIONID=e480176b-b0d4-4c96-8437-55eef3f1f6d8; Max-Age=86400; Expires=Thu, 14 Jul 2016 13:57:20 GMT; Domain=localhost; Secure; HTTPOnly
HTTP/1.1 401 Unauthorized
HTTP/1.1 500 Internal Server Error
Login response contains an error or a HTTP cookie for use in subsequent calls.
Timeseries API
The Timeseries API contains operations for getting metadata about the time series, and retrieving the data points.
Put Operation
echo "put sys.cpu.user 1234567890 1.0 tag1=value1 tag2=value2" | nc <host> <tcp_port>
echo "put sys.cpu.user 1234567890 1.0 tag1=value1 tag2=value2" | nc -u <host> <udp_port>
POST /api/put HTTP/1.1
{
"metric" : "sys.cpu.user",
"timestamp" : 1234567890,
"value" : 1.0,
"tags" : [
{
"key" : "tag1",
"value" : "value1"
},
{
"key" : "tag2",
"value" : "value2"
}
]
}
{
"operation" : "put",
"metric" : "sys.cpu.user",
"timestamp" : 1234567890,
"value" : 1.0,
"tags" : [
{
"key" : "tag1",
"value" : "value1"
},
{
"key" : "tag2",
"value" : "value2"
}
]
}
The put operation is used for sending time series data to Timely. A time series metric is composed of the following items:
Attribute | Type | Description |
---|---|---|
meric | string | The name of the metric |
timestamp | long | The timestamp in ms |
value | double | The value of this metric at this time |
tags | map | (Optional) Pairs of K,V strings to associate with this metric |
Suggest Operation
GET /api/suggest?type=metrics&q=sys.cpu.u&max=30 HTTP/1.1
POST /api/suggest HTTP/1.1
{
"type" : "metrics",
"q" : "sys.cpu",
"max" : 30
}
{
"operation" : "suggest",
"sessionId" : "<value of TSESSIONID>",
"type" : "metrics",
"q" : "sys.cpu",
"max" : 30
}
The suggest operation is used to find metrics, tag keys, or tag values that match the specified pattern. Returns a Suggest response. Input parameters are:
Attribute | Type | Description |
---|---|---|
type | string | one of metrics, tagk, tagv |
q | string | the query string |
max | integer | the maximum number of results |
Suggest Response
HTTP/1.1 200 OK
[
"sys.cpu.idle",
"sys.cpu.user",
"sys.cpu.wait"
]
HTTP/1.1 401 Unauthorized
HTTP/1.1 500 Internal Server Error
[
"sys.cpu.idle",
"sys.cpu.user",
"sys.cpu.wait"
]
The response to the suggest operation contains a list of suggestions based on the request parameters or an error. An HTTP 401 error will be returned on a missing or unknown TSESSIONID
cookie. A TextWebSocketFrame will be returned on a successful WebSocket request, otherwise a CloseWebSocketFrame will be returned.
Lookup Operation
GET /api/search/lookup?m=sys.cpu.user{host=*}&limit=3000 HTTP/1.1
POST /api/search/lookup HTTP/1.1
{
"metric": "sys.cpu.user",
"limit": 3000,
"tags":[
{
"key": "host",
"value": "*"
}
]
}
{
"operation" : "lookup",
"sessionId" : "<value of TSESSIONID>",
"metric": "sys.cpu.user",
"limit": 3000,
"tags":[
{
"key": "host",
"value": "*"
}
]
}
The lookup operation is used to find information in the meta table associated with the supplied metric or tag input parameters. Returns a Lookup response.
Attribute | Type | Description |
---|---|---|
metric | string | metric name or prefix. Used in HTTP POST and WebSocket |
m | string | metric name or prefix. Used in HTTP GET |
limit | int | (Optional default:25) maximum number of results |
tags | map | (Optional) Pairs of K,V strings to to match results against |
Lookup Response
HTTP/1.1 200 OK
{
"type":"LOOKUP",
"metric":"sys.cpu.idle",
"tags":{
"tag3":"*"
},
"limit":25,
"time":49,
"totalResults":1,
"results":[
{
"metric":null,
"tags":{
"tag3":"value3"
},
"tsuid":null
}
]
}
{
"type":"LOOKUP",
"metric":"sys.cpu.idle",
"tags":{
"tag3":"*"
},
"limit":25,
"time":49,
"totalResults":1,
"results":[
{
"metric":null,
"tags":{
"tag3":"value3"
},
"tsuid":null
}
]
}
The response to the lookup operation contains a set of metric names and tag set information that matches the request parameters. Response attributes are:
Attribute | Type | Description |
---|---|---|
type | string | constant value of LOOKUP |
metric | string | copy of the metric input parameter |
tags | map | copy of the tags input parameter |
limit | int | copy of the limit input parameter |
time | int | operation duration in ms |
totalResults | int | number of results |
results | array | array of result objects that contain matching metric names and tag sets |
Query Operation
GET /api/query?start=1356998400&end=1356998460&m=sum:rate{false,100,0}:sys.cpu.user{host=*}{rack=r1|r2}&tsuid=sum:000001000002000042,000001000002000043 HTTP/1.1
POST /api/query HTTP/1.1
{
"start": 1356998400,
"end": 1356998460,
"queries": [
{
"aggregator": "sum",
"metric": "sys.cpu.user",
"rate": "true",
"rateOptions": {
"counter":false,
"counterMax":100,
"resetValue":0
},
"downsample" : "1m-max",
"tags": {
"host": "*",
"rack": "r1"
},
"filters": [
{
"type":"wildcard",
"tagk":"host",
"filter":"*",
"groupBy":true
},
{
"type":"literal_or",
"tagk":"rack",
"filter":"r1|r2",
"groupBy":false
}
]
},
{
"aggregator": "sum",
"tsuids": [
"000001000002000042",
"000001000002000043"
]
}
]
}
{
"operation" : "query",
"sessionId" : "<value of TSESSIONID>",
"start" : 1356998400,
"end" : 1356998460,
"queries" : [
{
"aggregator" : "sum",
"metric" : "sys.cpu.user",
"rate" : "true",
"rateOptions" : {
"counter" : false,
"counterMax" : 100,
"resetValue" : 0
},
"downsample" : "1m-max",
"tags" : {
"host" : "*",
"rack" : "r1"
},
"filters" : [
{
"type" : "wildcard",
"tagk" : "host",
"filter" : "*",
"groupBy" : true
},
{
"type" : "literal_or",
"tagk" : "rack",
"filter" : "r1|r2",
"groupBy" : false
}
]
}
]
}
The query operation is used to find time series data that matches the submitted query parameters. Returns a Query response.
Attribute | Type | Description |
---|---|---|
start | long | start time in ms for this query |
end | long | end time in ms for this query |
queries | array | array of metric sub query types. Used in HTTP Post or WebSocket |
Query Response
HTTP/1.1 200 OK
{
"metric" : "sys.cpu.user",
"tags" :{
"tag1" : "value1"
},
"aggregatedTags" :[],
"dps" : {
"1468954529" : 1.0,
"1468954530" : 3.0
}
}
[{
"metric" : "sys.cpu.user",
"tags" :{
"tag1" : "value1"
},
"aggregatedTags" :[],
"dps" : {
"1468954529" : 1.0,
"1468954530" : 3.0
}
}]
The response to the query operation contains a set of an array of time series data points. Each array element contains the metric and associated set of tags along with the metric values and associated timestamps. Reponse attributes are:
Attribute | Type | Description |
---|---|---|
metric | string | metric name for this time series |
tags | map | tags associated with this time series |
aggregatedTags | map | not used |
dps | map | map of timestamp to metric value |
Metric SubQuery Type
TODO
Aggregators Operation
TODO
Aggregators Response
TODO
Metrics Operation
TODO
Metrics Response
TODO
Subscription API
The subscription API allows the user to subscribe to metrics data using WebSockets. The create, add, remove, and close operations are expected to be Text frames and do not return a response for successful invocation. They return a Close frame with a message upon error condition. Each subscription is identified by a subscription id, a unique string, that the client will use to manipulate the subscription. This allows a client to create multiple subscriptions, using different subscription ids, on the same WebSocket channel.
Metric Response
{
"metric" : "sys.cpu.user",
"timestamp": 1469028728091,
"value": 1.0,
"tags":
[
{
"key": "rack",
"value": "r1"
},{
"key": "tag3",
"value": "value3"
},{
"key": "tag4",
"value": "value4"
}
],
"subscriptionId": "<unique id>",
"complete": false
}
The Metric response contains the metric name, tag set, value for the metric, and the timestamp. Unlike the timeseries api, the subscription responses will include the viz
tag if it exists on the data.
Create Operation
{
"operation" : "create",
"subscriptionId" : "<unique id>"
}
Initialize this WebSocket connection for subscription requests. This method does not return a response.
Add Operation
{
"operation" : "add",
"subscriptionId" : "<unique id>",
"metric" : "sys.cpu.user",
"tags" : null,
"startTime" : null,
"endTime" : null,
"delayTime" : 1000
}
Subscribe to metrics that match the metric name and optional tag set. Only one subscription per metric
is supported. The Timely server will return Metric responses over the WebSocket channel that match the metric
and tags
in the request. The Timely server will return metrics that are older than the startTime
, which can be zero to return all currently stored data. When all data has been returned, the Timely server will either send a completed message, or wait delayTime
ms before looking for new data, depending on the value of endTime. If endTime has been specified, then the Timely server will send a final message with complete
set to true. Otherwise, the Timely server maintains a pointer to the last metric returned, so it will not return data that is newly inserted but older than the last returned metric. Use the timely.websocket.subscription-lag
server configuration property to determine how close to current time the subscriptions will be. This will depend on your deployment. For example, if the timely.accumulo.write.latency
configuration property is 30s, then you may want to set the lag to greater than 30s to ensure that all data has arrived.
Attribute | Type | Description |
---|---|---|
metric | string | metric name |
tags | map | (Optional) Map of K,V strings to use in the query. |
startTime | long | (Optional, default 0) Return metrics after this time (in ms) |
endTime | long | (Optional, default 0) If specified, stop returning data at this time (in ms) |
delayTime | long | Wait time to look for the arrival of new data. |
Remove Operation
{
"operation" : "remove",
"subscriptionId" : "<unique id>",
"metric" : "sys.cpu.user"
}
Remove the subscription for this metric
. Data for this subscription will no longer be sent back to the user.
Attribute | Type | Description |
---|---|---|
metric | string | metric name |
Close Operation
{
"operation" : "close",
"subscriptionId" : "<unique id>"
}
Remove all subscriptions associated with this WebSocket channel.
Developer Information
Building Timely
Timely requires a Java 8 and uses Maven for compiling source, running tests, and packaging distributions. To build the CollectD plugins, activate the collectd
profile. Below are 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 package -Pcollectd | Runs findbugs and creates a distribution, includes the collectd plugins |
mvn verify | Runs integration tests |
mvn verify site | Creates the site |
Netty Pipeline Design
Timely supports multiple protocols, each having their own Netty pipeline. Each pipeline is set up in Server.java and includes Netty transport specific handlers, followed by a Decoder (TcpDecoder, HttpRequestDecoder, WebSocketRequestDecoder) that transforms the input request to a Java object. Handlers for each request Java object then follow and will perform request type specific actions and may or may not emit a response to the client in the format required by the transport (bytes for TCP, HTTP message for HTTP, web socket frame for WebSocket).
A single request type (e.g. VersionRequest) can be used across different protocols. Annotations for the different transports are used in the different decoders to match a request type to an operation or path. For example “version” is matched to a VersionRequest in the TCP protocol and it’s matched to “/version” in the HTTP protocol. Request objects will typically also implement an interface for each protocol for which they have a corresponding annotation. The interface methods are called from the protocol decoder to deserialize the message sent across the wire to a Java object.
Adding a new operation / request object:
- Create the object in the timely.api.request package and apply appropriate transport annotations
- Add a Handler for your request type in the appropriate transport channels
- Add tests for each transport decoder to ensure that your object is deserialized properly
- Add test cases to the appropriate integration tests
- Update the README api section.
Maintaining the Documentation
This documentation is created using Slate. To build you will need to install some Ruby dependencies described here. Once you have the dependencies, then you will want to checkout the slate branch to modify the documentation.
Viewing your changes locally
From the slate branch, execute bundle exec middleman server
and go to http://localhost:4567 in your browser
Pushing changes to gh-pages branch
From the slate branch, execute bundle exec middleman build --clean
which will generate the static pages in a directory called build
. Move the build directory somewhere and checkout the gh-pages branch. Copy the contents of the build directory into the gh-pages branch, commit, and push.
Updating Slate
The slate branch is a point-in-time copy of the master branch at https://github.com/lord/slate. You can update our slate branch to get new features using these instructions
Examples
WebSocket Example
Timely will serve up files located in the bin/webapp
directory. The source of the index.html file contains an example of how to use the Subscription Api with WebSockets. For this example to work with the standalone server, you first need to create some user certificates signed by the certificate authority that Timely is using. If you followed the instructions in the SSL Setup section, then you just need to do the following:
Note: When prompted for input parameters during the certificate request creation, enter the value
example.com
for the Common Name. In the defaultconf/security.xml
file it is looking for this value in X509 login requests.
openssl genrsa -out timely-user.key 4096
openssl req -new -key timely-user.key -sha256 -nodes -out timely-user.csr
openssl x509 -req -in timely-user.csr -CA CA.pem -CAkey CA.key -CAcreateserial -out timely-user.crt -days 365
openssl pkcs12 -export -out timely-user.p12 -inkey timely-user.key -in timely-user.crt -certfile CA.pem
- Next, load the timely-user.p12 file into your web browser.
- Then, start the standalone server.
- Finally, navigate to
https://localhost:54322/webapp/index.html
in your web browser.
Release Notes
0.0.5
FEATURES
- Made changes and tested with Grafana 4.4.3 (alerting not supported yet)
- Updated provided dashboards
NOTES
- Build modified to not build collectd modules by default, use the
collectd
maven profile at build time
0.0.4
FEATURES
- Added a Timely client library that contains Java code for interacting with the Timely API. Not all operations are finished.
- Added an analytics module for writing Apache Flink jobs
- Exposed Subscription Scanner configuration settings in the Timely configuration
PERFORMANCE
- AgeOff performance is greatly improved with a new iterator
- Added netty-transport-native-epoll as an optional runtime dependency
NOTES
- Timely has not been tested with Grafana 4.x
- The aggregation function (not the downsample aggregration function) in Grafana did not work prior to this release, it now works.
0.0.3
FEATURES
- Configuration file format changed to YAML due to use of Spring Configuration and Spring Boot
- Removed Double Lexicoder in Accumulo Value, requires removal of old data
- Added support for put operations in binary form using Google FlatBuffers
- Updated docker image
- Ageoff (in days) for individual metrics now possible, default value supported. Ageoff iterators are removed and re-applied during startup.
- Timely works with Accumulo 1.7 and 1.8
- Added support for PUT in text and binary form over UDP
PERFORMANCE
- Modified code to use one ageoff iterator instead of a stack of them
- Removed interpolation code and code that handles counters in a special manner. This should be done in the client.
- Moved rate calculation to the tablet server by using an iterator that groups time series together and then applies a filter
NOTES
- This list of jars that are needed on the tablet server has increased and now includes:
- commons-lang3
- commons-collections4
- guava
- timely-client
- timely-server
Additionally, the Accumulo classloader has to be configured for post-delegation given that the version of Guava that we depend on is newer than what Accumulo and Hadoop uses.
- The counter checkbox in the Grafana U/I does nothing. We are looking to fix this up in the next release.