Compare commits

...
46 Commits
Author SHA1 Message Date
Dave Marion 053d15c559 Set version in pom to 0.0.2 for tag 2016-07-27 08:22:15 -04:00
Dave Marion b3a479636b Add ASL badge to README 2016-07-26 14:33:21 -04:00
Dave Marion 8539986faf re #55, serve up favicon.ico if it exists 2016-07-26 13:57:41 -04:00
Drew Farris cbdf28c510 This >really< fixes #54 to work around nulls at deserialization time 2016-07-26 13:24:21 -04:00
klucar 670cc7769d Transparent favicon 2016-07-26 12:20:23 -04:00
klucar 22a9be3ba2 Generated favicon.ico 2016-07-26 12:17:13 -04:00
Dave Marion 2836b03366 Removed README, it's already in the docs 2016-07-26 08:56:36 -04:00
Dave Marion 64bde2189e fixes #54, protect against NPE 2016-07-26 08:49:17 -04:00
Dave Marion 4d41c8e19a fix formatting 2016-07-25 13:31:24 -04:00
Dave Marion 08a095e349 Fix mistake in README 2016-07-25 13:30:50 -04:00
Dave Marion fc0f2b8356 Remove READMEs and associated Screenshots, they are now in the slate & gh-pages branches 2016-07-25 13:29:54 -04:00
Dave Marion e9c0f63478 re #50, return -1 when not connected and use fqdn as hostname when no period in fqdn 2016-07-25 11:04:13 -04:00
Dave Marion 9c01d00686 Log errors that occur in the pipeline 2016-07-25 10:42:52 -04:00
Klucar dd8d379292 Fixes #53 2016-07-22 13:48:28 -04:00
Klucar 1acd5ce5cb updated Browser Test text to Browser Cert Check 2016-07-22 13:21:40 -04:00
Klucar aa3ac901b6 grafana timely logo update 2016-07-22 13:19:12 -04:00
Klucar 2c6270f23e retrieved websocket host from browser window 2016-07-22 09:56:32 -04:00
Dave Marion f6728b4e8d Add logo to README 2016-07-21 16:03:17 -04:00
klucar fb0590d778 Merge of Grafana app branch 2016-07-21 15:42:35 -04:00
Dave Marion f94ec78c66 closes #43, release reference to ByteBuf created in TcpDecoder. 2016-07-21 11:38:48 -04:00
Dave Marion 3fddbb4c6f closes #41, added rules for snmp plugin 2016-07-21 08:49:15 -04:00
Dave Marion e841ac90b8 removed unused imports 2016-07-18 12:48:49 -04:00
Dave Marion 203a97d7db Don't log known error condition 2016-07-15 13:27:06 -04:00
Dave Marion 4d19538c77 fixes #36, separate the request operations from the protocols. 2016-07-15 11:51:59 -04:00
Dave Marion e83356cb74 closes #45, add path to cookie 2016-07-15 09:26:59 -04:00
Dave Marion 5f1beb6045 closes #42 - remove grafana redirect 2016-07-12 17:50:40 -04:00
Dave Marion 12ee77386b closes #37 - update Netty to 4.0.38.Final 2016-07-06 08:26:07 -04:00
Dave Marion accbb2af9a closes #27, added web socket support to provide ability to create/add/remove subscriptions to receive metric data 2016-07-05 14:35:08 -04:00
Dave Marion 041cee1a80 closes #35, redirect http to https, which returns a 404 and a Strict-Transport-Security header 2016-07-05 09:52:32 -04:00
Dave Marion f8efdbf240 closes #34 - clean up warnings. Only allow file server to access /webapp 2016-06-30 14:34:24 -04:00
Jim Klucar ca4155be6f fixed test dashboard id 2016-06-30 11:07:32 -04:00
klucar b3c7b143e0 changed queries to regular expressions 2016-06-30 07:41:12 -04:00
Dave Marion 42d31efff8 Merge pull request #32 from NationalSecurityAgency/docker
re #31 Changes for using Docker
2016-06-29 12:27:48 -04:00
Dave Marion 56e90ef693 re #30, add test for processing all values of the first tag found in meta table when no tags in the query. Fix regex tests. 2016-06-29 11:34:21 -04:00
klucar 6345efa996 changed localhost to 127.0.0.1 to match original scripts 2016-06-28 22:04:30 -04:00
klucar 701f841cfe fixed .so vs .jnilib netty tcnative extraction. fixed overriding timely host and port in launch scripts 2016-06-28 22:01:01 -04:00
Jim Klucar 4b6de88e25 Update README.docker 2016-06-28 09:48:07 -04:00
Jim Klucar de9b0df49e Update README.docker 2016-06-28 09:46:28 -04:00
klucar b8418e4e19 created 3-container example system using docker-compose 2016-06-28 07:20:59 -04:00
Dave Marion d5cec191e2 closes #28, note that the binary is not fully portable 2016-06-27 19:33:10 -04:00
Dave Marion 13feefde31 closes #1 - added utility to generate split points for the metric table from the meta table 2016-06-24 08:43:11 -04:00
Dave Marion 86c9d8835d closes #26: removed unused code in downsample iterator thus commons-lang3 is no longer required in the tserver classpath. Re-ordered tags based on the counts from the meta cache 2016-06-23 16:06:11 -04:00
Dave Marion b8da5152e0 closes #17 - regex support for tag values 2016-06-23 13:55:27 -04:00
Dave Marion e73564c578 closes #24, added note to README about tag order in queries 2016-06-22 16:18:59 -04:00
Dave Marion f42696ddfc Added static file server for #22 2016-06-22 16:09:46 -04:00
Dave Marion 7e3a85050d Bump version to 0.0.2-SNAPSHOT 2016-06-22 14:33:26 -04:00
224 changed files with 10946 additions and 1776 deletions

No files matched your search

+5 -177
View File
@@ -1,180 +1,8 @@
# Timely
---
![Timely](timely-readme-logo.png)
Timely is a time series database application that provides secure access to time series data. Timely is written in Java and designed to work with [Apache Accumulo] (http://accumulo.apache.org/) and [Grafana] (http://www.grafana.org).
[![Apache License][li]][ll]
# Design
---
Being big users of Apache Accumulo, when we required a time series database application we looked to use software that was compatible with it. Specifically, we started with [OpenTSDB] (http://www.opentsdb.net) and Eric Newton's [Accumulo-OpenTSDB] (https://github.com/ericnewton/accumulo-opentsdb) project to bridge them together and Grafana for visualization. We ultimately ran into some issues that we could not work around. After much discussion we decided to replace OpenTSDB with something more native to Apache Accumulo. The implementation of Timely started with the following constraints and assumptions:
1. We need to show progress quickly.
2. We started with Grafana, so we didn't need our own user interface.
3. We already had experience with OpenTSDB, so we started with its API.
4. Space is cheap, ingest and query speed are more important.
5. Metrics will be kept short term.
6. The number of tags used for each metric will be small \( < 10 \).
7. We can create a better API and Grafana plugin at a later time.
## Building
Timely uses Maven for compiling source, running tests, and packaging distributions. Below is a set of useful Maven lifecyles that we use:
Command | Description
--------|------------
mvn compile | Compiles and formats the source
mvn test | Runs unit tests
mvn package | Runs findbugs and creates a distribution
mvn verify | Runs integration tests
mvn verify site | Creates the site
The [CollectD] (http://collectd.org/) plugins require that CollectD is installed locally as the `/usr/share/collectd/java/collectd-api.jar` file is a dependency.
## Deployment
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.
Create a distribution and untar it somewhere. Modify the `conf/timely.properties` file appropriately. Then, copy the `timely-server` and `commons-lang3` jar files to your Accumulo tservers. Next, launch Timely by running the `bin/timely-server.sh` script.
If you just want to kick the tires without having to install and setup Apache Hadoop and Apache Accumulo, then you can start Timely with the `bin/timely-standalone.sh` script. This will start an Accumulo MiniCluster in the background, but be aware that the standalone instance will not save your metric data across restarts.
## API
The current Timely API is compatible with a subset of the OpenTSDB API. We currently support the OpenTSDB TCP put API and we have partially implemented the following HTTP operations that the Grafana OpenTSDB datasource uses:
* /api/aggregators
* /api/query
* /api/search/lookup
* /api/suggest
We also provide the following additional endpoints:
* /login
This endpoint should be used for non-anonymous access. It supports a POST request with username and password, or a GET request with SSL mutual authentication.
* /api/metrics
This endpoint reports the metric names and tags that have been pushed to Timely. Eviction of this information is configurable, so it may reflect metrics that have been reported in the past but are no longer being actively reported. This could be useful in creating graphs in Grafana (so you won't have to go digging in the `meta` table in Accumulo).
## Storage Format
Metrics sent to Timely are stored in two Accumulo tables, `meta` and `metrics`. The `meta` table stores information about the metric names and tags. It's format is:
Row | ColumnFamily | ColumnQualifier | Value
----|:-------------|:----------------|:-----
m:metric | | |
t:metric | tagKey | |
v:metric | tagKey | tagValue |
The `metrics` table stores the datapoints using the following format:
Row | ColumnFamily | ColumnQualifier | Value
----|:-------------|:----------------|:-----
metric\timestamp | tagKey=tagValue | tagKey=tagValue,tagKey=tagValue,... | metricValue
As an example, if you sent the following metric to the put api
```
put sys.cpu.user 1447879348291 2.0 rack=r001 host=r001n01 instance=0
```
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
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
## Configuration
The `NUM_SERVER_THREADS` variable in the `timely-server.sh` script controls how many threads are used in the Netty event group for TCP and HTTP operations. The TCP and HTTP groups use a different event group, so if you set the value to 8, then you will have 8 threads for TCP operations and 8 threads for HTTP operations. The properties file in the `conf` directory supports the following properties:
Property | Description | Default Value
:--------|:------------|:-------------
timely.ip | The ip address where the Timely server is running |
timely.port.put | The port that will be used for processing put requests |
timely.port.query | The port that will be used for processing query requests |
timely.instance\_name | The name of your Accumulo instance |
timely.zookeepers | The list of Zookeepers for your Accumulo instance |
timely.username | The username that Timely will use to connect to Accumulo |
timely.password | The password of the Accumulo user |
timely.table | The name of the metrics table | timely.metrics
timely.meta | The name of the meta table | timely.meta
timely.write.latency | The Accumulo BatchWriter latency | 5s
timely.write.threads | The Accumulo BatchWriter number of threads | [default](https://github.com/apache/accumulo/blob/master/core/src/main/java/org/apache/accumulo/core/client/BatchWriterConfig.java#L49)
timely.write.buffer.size | The Accumulo BatchWriter buffer size | [default](https://github.com/apache/accumulo/blob/master/core/src/main/java/org/apache/accumulo/core/client/BatchWriterConfig.java#L40)
timely.metric.age.off.days | The number of days to keep metrics | 7
timely.cors.allow.any.origin | Allow any origin in cross origin requests (true/false) | false
timely.cors.allow.null.origin | Allow null origins in cross origin requests (true/false) | false
timely.cors.allowed.origins | List of allowed origins in cross origin requests (can be null or comma separated list)
timely.cors.allowed.methods | List of allowed methods for cross origin requests | <ul><li>DELETE</li><li>GET</li><li>HEAD</li><li>OPTIONS</li><li>PUT</li><li>POST</li></ul>
timely.cors.allowed.headers | Comma separated list of allowed HTTP headers for cross origin requests | content-type
timely.cors.allow.credentials | Allow credentials to be passed in cross origin requests (true/false) | true
timely.scanner.threads | Number of BatchScanner threads to be used in a query | 4
timely.metrics.report.tags.ignored | Comma separated list of tags which will not be shown in the /api/metrics response |
timely.meta.cache.expiration.minutes | Number of minutes after which unaccessed meta information will be purged from the meta cache | 60
timely.meta.cache.initial.capacity | Initial capacity of the meta cache | 2000
timely.meta.cache.max.capacity | Maximum capacity of the meta cache | 10000
timely.ssl.certificate.file | Public certificate to use for the Timely server (x509 pem format) |
timely.ssl.key.file | Private key to use for the Timely server (in pkcs8 format) |
timely.ssl.key.pass | Password to the private key |
timely.ssl.use.generated.keypair | Use a generated certificate/key pair - useful for testing | false
timely.ssl.trust.store.file | Certificate trust store (a concatenated list of trusted CA x509 pem certificates) |
timely.ssl.use.openssl | Use OpenSSL (vs JDK SSL) | true
timely.ssl.use.ciphers | List of allowed SSL ciphers | see Configuration.java
timely.session.max.age | Setting for max age of session cookie (in seconds) | 86400
timely.http.address | Address for the Timely server, used for the session cookie domain |
grafana.http.address | Address for the Grafana server |
timely.allow.anonymous.access | Allow anonymous access | false
timely.visibility.cache.expiration.minutes | Column Visibility Cache Expiration (minutes) | 60
timely.visibility.cache.initial.capacity | Column Visibility Cache Initial Capacity | 2000
timely.visibility.cache.max.capacity | Column Visibility Cache Max Capacity | 10000
## Security
Timely allows users to optionally label their data using Accumulo column visibility expressions. To enable this feature, users should put the expressions in a tag named `viz`. Timely will [flatten] (http://accumulo.apache.org/1.7/apidocs/org/apache/accumulo/core/security/ColumnVisibility.html#flatten%28%29) this expression and store it in the column visibility of the data point in the metrics table. Column visibilities are *not* stored in the meta table, so anyone can see metric names, tag names, and tag values.
Timely provides HTTPS access to the query endpoints. It is possible to allow anonymous access to these endpoints which will allow anyone to see unlabeled data. Timely uses Spring Security to configure user authentication and user role information. Users must call the `/login` endpoint for authentication and Timely will respond by setting a HTTP cookie with a session id. The response to a successful login will be a temporary redirect (HTTP 307) to the configured address for the Grafana server. Grafana itself must be configured to use https for this to work properly. For more information see the [Quick Start] (quick-start/QUICK_START.md) documentation.
## Tuning
1. Each thread in the Timely server that is used for processing TCP put operations has its own BatchWriter. Each BatchWriter honors the `timely.write.latency` and `timely.write.threads` configuration property, but the buffer size for each BatchWriter is `timely.write.buffer.size` divided by the number of threads. For example, if you have 8 threads processing put operations and the following settings, then you will have 8 BatchWriters each using 2 threads with a 30s latency and a maximum buffer size of 128M:
```
timely.write.latency=30s
timely.write.threads=2
timely.write.buffer.size=1G
```
2. The `timely.scanner.threads` property is used for BatchScanners on a per query basis. If you set this to 32 and have 8 threads processing HTTP operations, then you might have 256 threads concurrently querying your tablet servers. Be sure to set your ulimits appropriately.
3. The Timely server contains an object called the meta cache, which is a cache of the keys that would be inserted into the meta table if they did not already exist. This cache is used to reduce the insert load of duplicate keys into the meta table and to serve up data to the `/api/metrics` endpoint. The meta cache is an object that supports eviction based on last access time and can be tuned with the `timely.meta.cache.*` properties.
## NOTES
1. Not shown in the examples is the encoding of the row and value. You can apply the Timely formatter to your metrics table using the Accumulo shell command:
```
config -t timely.metrics -s table.formatter=timely.util.TimelyMetricsFormatter
```
2. You can create split points for your metrics table based on the 'm' column family in the meta table.
3. You can lower the `table.scan.max.memory` property on your metrics table in an attempt to get data back faster from the tablet servers.
4. If you don't mind losing some metric data in the event of an Accumulo tablet server death, you can set the `table.walog.enabled` property to false and the `table.durability` property to none on your metrics table. This should speed up ingest a little.
Timely is a time series database application that provides secure access to time series data. Timely is written in Java and designed to work with [Apache Accumulo] (http://accumulo.apache.org/) and [Grafana] (http://www.grafana.org). Documentation is located [here] (https://nationalsecurityagency.github.io/timely/).
[li]: http://img.shields.io/badge/license-ASL-blue.svg
[ll]: https://www.apache.org/licenses/LICENSE-2.0
-19
View File
@@ -1,19 +0,0 @@
# Write NSQ plugin for CollectD
To build, run ```mvn clean package```. Place the resulting jar file somewhere on the local filesystem and add the following to the
collectd configuration file
```
LoadPlugin java
<Plugin java>
JVMArg "-verbose:jni"
JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar:<path_to_jar>/collectd-nsq-plugin-0.0.1-SNAPSHOT.jar"
JVMArg "-Xms1024m"
JVMArg "-Xmx1024m"
LoadPlugin "timely.collectd.plugin.WriteNSQPlugin"
<Plugin "timely.collectd.plugin.WriteNSQPlugin">
Host "<NSQHost[,NSQHost]>"
Port "<NSQHttpPort>"
</Plugin>
</Plugin>
```
+3 -7
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.1</version>
<version>0.0.2</version>
</parent>
<artifactId>collectd-nsq-plugin</artifactId>
<dependencies>
@@ -105,12 +105,8 @@
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.revelc.code</groupId>
<artifactId>
formatter-maven-plugin
</artifactId>
<versionRange>
[0.5.2,)
</versionRange>
<artifactId>formatter-maven-plugin</artifactId>
<versionRange>[0.5.2,)</versionRange>
<goals>
<goal>format</goal>
</goals>
-14
View File
@@ -1,14 +0,0 @@
# CollectD Plugin
The CollectD plugin formats metrics acquired by the other plugins and sends them to Timely. The plugin contains code for parsing and extracting information from metrics reported by some of the plugins. One special case is the metrics received by the [StatsD] (https://github.com/etsy/statsd) plugin. It supports processing of [NSQ] (http://nsq.io/) metrics by looking for StatsD metrics with the prefix 'nsq'.
It also supports parsing of the StatsD metrics coming from the Hadoop Metrics2 framework (a metric with 4 components separated by periods). In this case you will want to set the skip hostname property to true and send the metrics from Hadoop to a local CollectD instance that will add the hostname and other tags.
The plugin allows for other unspecified formats to be sent as-is, except in the following case: if the StatsD metric contains a period, everything up to the period will be used in an instance tag.
For example:
"foo:1|c" will end up as the metric 'statsd.foo'
"bar.foo:1|c" will end up as the metric 'statsd.foo' with an 'instance=bar' tag.
+3 -7
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.1</version>
<version>0.0.2</version>
</parent>
<artifactId>collectd-plugin-parent</artifactId>
<dependencies>
@@ -101,12 +101,8 @@
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.revelc.code</groupId>
<artifactId>
formatter-maven-plugin
</artifactId>
<versionRange>
[0.5.2,)
</versionRange>
<artifactId>formatter-maven-plugin</artifactId>
<versionRange>[0.5.2,)</versionRange>
<goals>
<goal>format</goal>
</goals>
@@ -64,7 +64,9 @@ public abstract class CollectDPluginParent {
String host = vl.getHost();
int idx = host.indexOf(PERIOD);
if (-1 != idx) {
tags.append(" host=").append(vl.getHost().substring(0, idx));
tags.append(" host=").append(host.substring(0, idx));
} else {
tags.append(" host=").append(host);
}
int nIdx = host.indexOf('n');
if (-1 != nIdx) {
@@ -167,6 +169,9 @@ public abstract class CollectDPluginParent {
} else if (vl.getPlugin().equals("ipmi")) {
metric.append("sys.ipmi.").append(vl.getType());
tags.append(INSTANCE).append(vl.getTypeInstance().replaceAll(" ", "_"));
} else if (vl.getPlugin().equals("snmp")) {
metric.append("sys.snmp.").append(vl.getType());
tags.append(INSTANCE).append(vl.getTypeInstance().replaceAll(" ", "_"));
} else if (notEmpty(vl.getTypeInstance()) && notEmpty(vl.getType()) && notEmpty(vl.getPluginInstance())
&& notEmpty(vl.getPlugin())) {
metric.append("sys.").append(vl.getPlugin()).append(PERIOD).append(vl.getType()).append(PERIOD)
@@ -185,6 +185,28 @@ public class PluginParentTest {
result);
}
@Test
public void testSnmp() throws Exception {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("snmp");
// vl.setPluginInstance("sda");
vl.setTime(TIME);
vl.setType("if_octets");
vl.setTypeInstance("Ethernet1");
vl.setValues(Collections.singletonList((Number) 0.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.snmp.if_octets 1456156976840 0.0 host=r01n01 rack=r01 addl1=foo instance=Ethernet1 sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testSensors() {
ValueList vl = new ValueList();
-19
View File
@@ -1,19 +0,0 @@
# Write Timely plugin for CollectD
To build, run ```mvn clean package```. Place the resulting jar file somewhere on the local filesystem and add the following to the
collectd configuration file
```
LoadPlugin java
<Plugin java>
JVMArg "-verbose:jni"
JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar:<path_to_jar>/collectd-timely-plugin-0.0.1-SNAPSHOT.jar"
JVMArg "-Xms1024m"
JVMArg "-Xmx1024m"
LoadPlugin "timely.collectd.plugin.WriteTimelyPlugin"
<Plugin "timely.collectd.plugin.WriteTimelyPlugin">
Host "<TimelyHost>"
Port "<TimelyPutPort>"
</Plugin>
</Plugin>
```
+3 -7
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.1</version>
<version>0.0.2</version>
</parent>
<artifactId>collectd-timely-plugin</artifactId>
<dependencies>
@@ -133,12 +133,8 @@
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.revelc.code</groupId>
<artifactId>
formatter-maven-plugin
</artifactId>
<versionRange>
[0.5.2,)
</versionRange>
<artifactId>formatter-maven-plugin</artifactId>
<versionRange>[0.5.2,)</versionRange>
<goals>
<goal>format</goal>
</goals>
@@ -52,7 +52,7 @@ public class WriteTimelyPlugin extends CollectDPluginParent implements CollectdC
}
} else {
Collectd.logWarning("Not writing to Timely, waiting to reconnect");
return 0;
return -1;
}
}
return 0;
+15
View File
@@ -0,0 +1,15 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>timely-app</directory>
<outputDirectory>timely-app</outputDirectory>
</fileSet>
</fileSets>
<includeBaseDirectory>false</includeBaseDirectory>
</assembly>
+33
View File
@@ -0,0 +1,33 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>timely-app</artifactId>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.2</version>
</parent>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<tarLongFileMode>posix</tarLongFileMode>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+2
View File
@@ -0,0 +1,2 @@
node_modules/
+65
View File
@@ -0,0 +1,65 @@
module.exports = function(grunt) {
require('load-grunt-tasks')(grunt);
grunt.loadNpmTasks('grunt-execute');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.initConfig({
clean: ["dist"],
copy: {
src_to_dist: {
cwd: 'src',
expand: true,
src: ['**/*', '!**/*.js', '!**/*.scss'],
dest: 'dist'
},
img_to_dist: {
cwd: 'src',
expand: true,
src: ['img/*'],
dest: 'dist/src/'
},
pluginDef: {
expand: true,
src: ['plugin.json', 'README.md'],
dest: 'dist',
}
},
watch: {
rebuild_all: {
files: ['src/**/*', 'plugin.json', 'README.md'],
tasks: ['default'],
options: {spawn: false}
},
},
babel: {
options: {
sourceMap: true,
presets: ["es2015"],
plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"],
},
dist: {
files: [{
cwd: 'src',
expand: true,
src: ['**/*.js'],
dest: 'dist',
ext:'.js'
}]
},
}
});
grunt.registerTask('default',
['clean',
'copy:src_to_dist',
'copy:pluginDef',
'copy:img_to_dist',
'babel'
]);
};
+3
View File
@@ -0,0 +1,3 @@
# Timely
Timely: a scalable, distributed, **secure** time series database.
+3
View File
@@ -0,0 +1,3 @@
# Timely
Timely: a scalable, distributed, **secure** time series database.
@@ -0,0 +1,10 @@
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-13">OpenTSDB metrics query</span>
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.target' placeholder="events.eventname"></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-13">Show Global Annotations?</span>
<editor-checkbox text="" model="ctrl.annotation.isGlobal"></editor-checkbox>
</div>
</div>
+52
View File
@@ -0,0 +1,52 @@
<div class="page-header">
<h1>Timely Login</h1>
</div>
<div ng-if="ctrl.datasources.length === 0">
<h4>No Timely datasources are defined.</h4>
<i><a href="datasources/new">Create one</a></i>
</div>
<div ng-repeat="source in ctrl.datasources" ng-init="d=source" ng-controller="AuthsCtrl as actrl">
<div class="gf-box">
<div class="gf-box-header p-x-1 p-y-1">
<h3 class="">{{d.name}}
<span class="pull-right">
<h6><i>https://{{d.jsonData.timelyHost}}:{{d.jsonData.httpsPort}}/login</i></h6>
</span>
</h3>
</div>
<div class="gf-box-body">
<div ng-if="d.jsonData.basicAuths" class="p-b-1">
<div class="gf-form">
<span class="gf-form-label width-7">
User
</span>
<input id="timlely-user" class="gf-form-input max-width-21" type="text" ng-model='actrl.basicAuthUser' placeholder="user" required></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">
Password
</span>
<input id="timely-password" class="gf-form-input max-width-21" type="password" ng-model='actrl.basicAuthPassword' placeholder="password" required></input>
</div>
</div>
<div ng-if="actrl.fetching" class="gf-row">
<div class="alert-{{actrl.fetching.status}} alert">
<div class="alert-title">{{actrl.fetching.title}}</div>
</div>
</div>
<div class="gf-form-button-row center-block">
<button type="submit" class="btn btn-success" ng-click="actrl.login(d)">
Login
</button>
</div>
</div>
</div>
</div>
+146
View File
@@ -0,0 +1,146 @@
'use strict';
System.register(['lodash'], function (_export, _context) {
"use strict";
var _, _createClass, AuthsCtrl;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
return {
setters: [function (_lodash) {
_ = _lodash.default;
}],
execute: function () {
_createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
_export('AuthsCtrl', AuthsCtrl = function () {
function AuthsCtrl(backendSrv, $rootScope, $timeout) {
_classCallCheck(this, AuthsCtrl);
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.backendSrv = backendSrv;
}
_createClass(AuthsCtrl, [{
key: 'login',
value: function login(datasource) {
this.fetching = {};
this.fetching.done = false;
this.fetching.show = true;
this.fetching.status = 'warning';
this.fetching.title = 'Fetching auths...';
//this.$rootScope.appEvent('alert-success', ['Authorization Cookie Received', '']);
//this.$rootScope.appEvent('alert-error', ['Authorization Failed', '']);
//this.$rootScope.appEvent('alert-warning', ['Fetching Authorizations', '']);
var uri = this._getURI(datasource.jsonData);
if (datasource.jsonData.basicAuths) {
if (this.basicAuthUser && this.basicAuthPassword) {
var authBlob = { username: this.basicAuthUser, password: this.basicAuthPassword };
this.doPostLogin(uri, authBlob);
} else {
this.$rootScope.appEvent('alert-warning', [datasource.name, 'requires User/Password']);
this.fetching.status = 'warning';
this.fetching.title = 'User/Password required';
return;
}
} else {
this.doGetLogin(uri);
}
}
}, {
key: 'doPostLogin',
value: function doPostLogin(uri, authBlob) {
this.doXHR('POST', uri, authBlob);
}
}, {
key: 'doGetLogin',
value: function doGetLogin(uri) {
this.doXHR('GET', uri);
}
}, {
key: 'doXHR',
value: function doXHR(verb, uri, data) {
if (!data) {
data = "";
}
var r = new XMLHttpRequest();
r.open(verb, uri, true);
r.withCredentials = true;
r.setRequestHeader('Content-Type', 'application/json');
r.onreadystatechange = function () {
this.handleLoginResult(r);
}.bind(this);
r.send(JSON.stringify(data));
}
}, {
key: 'handleLoginResult',
value: function handleLoginResult(response) {
// wait for readyState to be '4 => done'
if (response.readyState != 4) return;
this.fetching.done = true;
switch (response.status) {
case 200:
var cookie = (document.cookie.match(/^(?:.*;)?TSESSIONID=([^;]+)(?:.*)?$/) || [, null])[1];
console.log(cookie);
console.log(document.cookie);
this.fetching.status = 'success';
this.fetching.title = 'Login successful';
this.$rootScope.appEvent('alert-success', ['Success', '']);
break;
default:
this.fetching.status = 'warning';
this.fetching.title = response.statusText;
this.$rootScope.appEvent('alert-warning', [response.status + ': ' + response.statusText, '']);
break;
}
if (r.readyState != 4 || r.status != 200) return;
alert("Success: " + r.getResponseHeader('Set-Cookie'));
}
}, {
key: '_getURI',
value: function _getURI(jsonData) {
return 'https://' + jsonData.timelyHost + ':' + jsonData.httpsPort + '/login';
}
}, {
key: '_showResults',
value: function _showResults() {}
}]);
return AuthsCtrl;
}());
_export('AuthsCtrl', AuthsCtrl);
}
};
});
//# sourceMappingURL=auths.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../src/components/auths.js"],"names":[],"mappings":"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","file":"auths.js","sourcesContent":["import _ from 'lodash';\n\nexport class AuthsCtrl {\n\n constructor(backendSrv, $rootScope, $timeout){\n this.$rootScope = $rootScope;\n this.$timeout = $timeout;\n this.backendSrv = backendSrv;\n }\n\n login(datasource){\n this.fetching = {};\n this.fetching.done = false;\n this.fetching.show = true;\n this.fetching.status = 'warning';\n this.fetching.title = 'Fetching auths...';\n\n //this.$rootScope.appEvent('alert-success', ['Authorization Cookie Received', '']);\n //this.$rootScope.appEvent('alert-error', ['Authorization Failed', '']);\n //this.$rootScope.appEvent('alert-warning', ['Fetching Authorizations', '']);\n\n var uri = this._getURI(datasource.jsonData);\n\n if(datasource.jsonData.basicAuths){\n if( this.basicAuthUser && this.basicAuthPassword){\n var authBlob = {username: this.basicAuthUser, password: this.basicAuthPassword};\n this.doPostLogin(uri, authBlob);\n } else {\n this.$rootScope.appEvent('alert-warning', [datasource.name, 'requires User/Password']);\n this.fetching.status = 'warning';\n this.fetching.title = 'User/Password required';\n\n return;\n }\n } else {\n this.doGetLogin(uri);\n }\n\n }\n\n doPostLogin(uri, authBlob){\n this.doXHR('POST', uri, authBlob);\n }\n\n doGetLogin(uri){\n this.doXHR('GET', uri)\n }\n\n doXHR(verb, uri, data){\n if( !data){\n data = \"\";\n }\n var r = new XMLHttpRequest();\n r.open(verb, uri, true);\n r.withCredentials = true;\n r.setRequestHeader('Content-Type', 'application/json');\n r.onreadystatechange = function () {\n this.handleLoginResult(r);\n }.bind(this);\n r.send(JSON.stringify(data));\n }\n\n handleLoginResult(response){\n // wait for readyState to be '4 => done'\n if (response.readyState != 4) return;\n\n this.fetching.done = true;\n\n switch(response.status){\n case 200:\n var cookie = (document.cookie.match(/^(?:.*;)?TSESSIONID=([^;]+)(?:.*)?$/)||[,null])[1]\n console.log(cookie);\n console.log(document.cookie);\n \n this.fetching.status = 'success';\n this.fetching.title = 'Login successful';\n this.$rootScope.appEvent('alert-success', ['Success', '']);\n break;\n default:\n this.fetching.status = 'warning';\n this.fetching.title = response.statusText;\n this.$rootScope.appEvent('alert-warning', [response.status+': '+response.statusText,'']);\n break;\n }\n\n if (r.readyState != 4 || r.status != 200) return;\n alert(\"Success: \" + r.Line truncated
+80
View File
@@ -0,0 +1,80 @@
'use strict';
System.register(['lodash', 'app/core/core', 'plugins/timely-app/components/auths'], function (_export, _context) {
"use strict";
var _, coreModule, appEvents, AuthsCtrl, _createClass, AuthsPageCtrl;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
return {
setters: [function (_lodash) {
_ = _lodash.default;
}, function (_appCoreCore) {
coreModule = _appCoreCore.coreModule;
appEvents = _appCoreCore.appEvents;
}, function (_pluginsTimelyAppComponentsAuths) {
AuthsCtrl = _pluginsTimelyAppComponentsAuths.AuthsCtrl;
}],
execute: function () {
_createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
_export('AuthsPageCtrl', AuthsPageCtrl = function () {
function AuthsPageCtrl(backendSrv, datasourceSrv) {
_classCallCheck(this, AuthsPageCtrl);
this.datasourceSrv = datasourceSrv;
this.datasources = this.getTimelyDatasources();
coreModule.controller('AuthsCtrl', AuthsCtrl);
}
_createClass(AuthsPageCtrl, [{
key: 'getTimelyDatasources',
value: function getTimelyDatasources() {
var allSources = this.datasourceSrv.getAll();
var sources = _.map(allSources, function (value, key, col) {
var ret = {};
ret.meta = {};
ret.meta.type = col[key].meta.type;
ret.meta.id = col[key].meta.id;
ret.name = key;
ret.jsonData = col[key].jsonData;
return ret;
});
return _.filter(sources, function (d) {
return d.meta.type === 'datasource' && d.meta.id === 'timely-datasource';
});
}
}]);
return AuthsPageCtrl;
}());
_export('AuthsPageCtrl', AuthsPageCtrl);
AuthsPageCtrl.templateUrl = 'components/auths.html';
}
};
});
//# sourceMappingURL=authspage.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../src/components/authspage.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AACO,O;;AACC,gB,gBAAA,U;AAAY,e,gBAAA,S;;AACZ,e,oCAAA,S;;;;;;;;;;;;;;;;;;;;;+BAEK,a;AAEX,+BAAY,UAAZ,EAAwB,aAAxB,EAAsC;AAAA;;AACpC,eAAK,aAAL,GAAqB,aAArB;AACA,eAAK,WAAL,GAAmB,KAAK,oBAAL,EAAnB;AACA,qBAAW,UAAX,CAAsB,WAAtB,EAAmC,SAAnC;AACD;;;;iDAEqB;AACpB,gBAAI,aAAa,KAAK,aAAL,CAAmB,MAAnB,EAAjB;;AAEA,gBAAI,UAAU,EAAE,GAAF,CAAM,UAAN,EAAkB,UAAS,KAAT,EAAgB,GAAhB,EAAqB,GAArB,EAAyB;AACvD,kBAAI,MAAM,EAAV;AACA,kBAAI,IAAJ,GAAW,EAAX;AACA,kBAAI,IAAJ,CAAS,IAAT,GAAgB,IAAI,GAAJ,EAAS,IAAT,CAAc,IAA9B;AACA,kBAAI,IAAJ,CAAS,EAAT,GAAc,IAAI,GAAJ,EAAS,IAAT,CAAc,EAA5B;AACA,kBAAI,IAAJ,GAAW,GAAX;AACA,kBAAI,QAAJ,GAAe,IAAI,GAAJ,EAAS,QAAxB;AACA,qBAAO,GAAP;AACD,aARa,CAAd;AASA,mBAAO,EAAE,MAAF,CAAS,OAAT,EAAkB,UAAS,CAAT,EAAW;AAClC,qBAAQ,EAAE,IAAF,CAAO,IAAP,KAAgB,YAAhB,IAAgC,EAAE,IAAF,CAAO,EAAP,KAAc,mBAAtD;AACD,aAFM,CAAP;AAGD;;;;;;;;AAGH,oBAAc,WAAd,GAA4B,uBAA5B","file":"authspage.js","sourcesContent":["\nimport _ from 'lodash';\nimport {coreModule, appEvents} from 'app/core/core';\nimport {AuthsCtrl} from 'plugins/timely-app/components/auths';\n\nexport class AuthsPageCtrl {\n\n constructor(backendSrv, datasourceSrv){\n this.datasourceSrv = datasourceSrv;\n this.datasources = this.getTimelyDatasources();\n coreModule.controller('AuthsCtrl', AuthsCtrl);\n }\n\n getTimelyDatasources(){\n var allSources = this.datasourceSrv.getAll();\n\n var sources = _.map(allSources, function(value, key, col){\n var ret = {};\n ret.meta = {};\n ret.meta.type = col[key].meta.type;\n ret.meta.id = col[key].meta.id;\n ret.name = key;\n ret.jsonData = col[key].jsonData;\n return ret;\n });\n return _.filter(sources, function(d){\n return (d.meta.type === 'datasource' && d.meta.id === 'timely-datasource');\n });\n }\n\n}\nAuthsPageCtrl.templateUrl = 'components/auths.html';\n"]}
+10
View File
@@ -0,0 +1,10 @@
<h3 class="page-heading">Timely App Configuration</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label">Currently, all configuration is done in the Timely datasource.</span>
</div>
</div>
</div>
+27
View File
@@ -0,0 +1,27 @@
'use strict';
System.register([], function (_export, _context) {
"use strict";
var TimelyAppConfigCtrl;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
return {
setters: [],
execute: function () {
_export('TimelyAppConfigCtrl', TimelyAppConfigCtrl = function TimelyAppConfigCtrl() {
_classCallCheck(this, TimelyAppConfigCtrl);
});
_export('TimelyAppConfigCtrl', TimelyAppConfigCtrl);
TimelyAppConfigCtrl.templateUrl = 'components/config.html';
}
};
});
//# sourceMappingURL=config.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../src/components/config.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;qCACa,mB,GAEX,+BAAc;AAAA;AAEb,O;;;;AAGH,0BAAoB,WAApB,GAAkC,wBAAlC","file":"config.js","sourcesContent":["\nexport class TimelyAppConfigCtrl {\n\n constructor() {\n \n }\n\n}\nTimelyAppConfigCtrl.templateUrl = 'components/config.html';\n"]}
+53
View File
@@ -0,0 +1,53 @@
<div class="page-header">
<h1>Metrics Browser</h1>
</div>
<div ng-hide="ctrl.hasDatasource" >
<h4>No Timely datasources are defined.</h4>
<i><a href="datasources/new">Create one</a></i>
</div>
<div class="gf-box" ng-show="ctrl.hasDatasource">
<div class="gf-box-header">
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-label width-7 p-y-1 p-x-1">Datasource</span>
<div class="gf-form-select-wrapper gf-form-select-wrapper--has-help-icon max-width-24">
<select class="gf-form-input"
ng-model="ctrl.source"
ng-options="d.name for d in ctrl.datasources"
ng-change=ctrl.updateDatasource()></select>
<info-popover mode="right-absolute">
Select a Datasource<br>
</info-popover>
</div>
</div>
</div>
</div>
<div class="gf-box-body">
<div class="grafana-options-table">
<div class="gf-form width-20 pull-right">
<input type="text" class="gf-form-input" placeholder="filter" ng-model="searchText">
</input>
</div>
<table>
<tr>
<th>Metric</th>
<th>Tags</th>
</tr>
<tr ng-repeat="metric in ctrl.metrics | filter:searchText">
<td>{{metric.metric}}</td>
<td class="p-x-1" ng-repeat="tag in metric.tags">
{{tag.name}} = {{tag.values}}
</td>
</tr>
</table>
</div>
</div>
</div>
+124
View File
@@ -0,0 +1,124 @@
'use strict';
System.register(['lodash', 'plugins/timely-app/components/authspage'], function (_export, _context) {
"use strict";
var _, AuthsPageCtrl, _createClass, MetricsPageCtrl;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
return {
setters: [function (_lodash) {
_ = _lodash.default;
}, function (_pluginsTimelyAppComponentsAuthspage) {
AuthsPageCtrl = _pluginsTimelyAppComponentsAuthspage.AuthsPageCtrl;
}],
execute: function () {
_createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
_export('MetricsPageCtrl', MetricsPageCtrl = function () {
function MetricsPageCtrl(backendSrv, datasourceSrv) {
_classCallCheck(this, MetricsPageCtrl);
this.hasDatasource = false;
this.backendSrv = backendSrv;
var apc = new AuthsPageCtrl(backendSrv, datasourceSrv);
this.datasources = apc.getTimelyDatasources();
if (this.datasources.length > 0) {
this.source = this.datasources[0];
this.updateDatasource();
}
}
_createClass(MetricsPageCtrl, [{
key: 'updateDatasource',
value: function updateDatasource() {
var _this = this;
this._clearMetrics();
var uri = this._getURI(this.source.jsonData);
this.backendSrv.request({ method: 'GET',
url: uri,
withCredentials: true,
params: "" }).then(function (response) {
_this.metrics = _this._transformMetrics(response.metrics);
_this.hasDatasource = true;
});
}
}, {
key: '_clearMetrics',
value: function _clearMetrics() {
this.metrics = [];
}
}, {
key: '_transformMetrics',
value: function _transformMetrics(metrics) {
var ret = [];
_.each(metrics, function (value, idx) {
var metric = {};
metric.metric = value.metric;
metric.tags = [];
_.map(value.tags, function (tag, key) {
// result || (result = {});
// result['name'] = tag.key;
// result['values'] || (result['values'] = []);
// result['values'].push(tag.value);
var gIdx = _.findIndex(metric.tags, { 'name': tag.key });
var grouped;
if (gIdx == -1) {
grouped = { name: '', values: [] };
} else {
grouped = metric.tags[gIdx];
}
grouped.values.push(tag.value);
grouped.values = _.sortBy(_.uniq(grouped.values));
grouped.name = tag.key;
if (gIdx > -1) {
metric.tags[gIdx] = grouped;
} else {
metric.tags.push(grouped);
}
}.bind(metric));
metric.tags = _.sortBy(metric.tags, 'name');
ret.push(metric);
}.bind(ret));
return _.sortBy(ret, 'metric');
}
}, {
key: '_getURI',
value: function _getURI(jsonData) {
return 'https://' + jsonData.timelyHost + ':' + jsonData.httpsPort + '/api/metrics';
}
}]);
return MetricsPageCtrl;
}());
_export('MetricsPageCtrl', MetricsPageCtrl);
MetricsPageCtrl.templateUrl = 'components/metrics.html';
}
};
});
//# sourceMappingURL=metrics.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../src/components/metrics.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AACO,O;;AACC,mB,wCAAA,a;;;;;;;;;;;;;;;;;;;;;iCAEK,e;AAEX,iCAAY,UAAZ,EAAwB,aAAxB,EAAsC;AAAA;;AACpC,eAAK,aAAL,GAAqB,KAArB;AACA,eAAK,UAAL,GAAkB,UAAlB;AACA,cAAI,MAAM,IAAI,aAAJ,CAAkB,UAAlB,EAA8B,aAA9B,CAAV;AACA,eAAK,WAAL,GAAmB,IAAI,oBAAJ,EAAnB;AACA,cAAI,KAAK,WAAL,CAAiB,MAAjB,GAA0B,CAA9B,EAAgC;AAC9B,iBAAK,MAAL,GAAc,KAAK,WAAL,CAAiB,CAAjB,CAAd;AACA,iBAAK,gBAAL;AACD;AACF;;;;6CAEiB;AAAA;;AAChB,iBAAK,aAAL;AACA,gBAAI,MAAM,KAAK,OAAL,CAAa,KAAK,MAAL,CAAY,QAAzB,CAAV;AACA,iBAAK,UAAL,CAAgB,OAAhB,CACE,EAAE,QAAQ,KAAV;AACE,mBAAK,GADP;AAEE,+BAAiB,IAFnB;AAGE,sBAAQ,EAHV,EADF,EAKE,IALF,CAME,oBAAY;AACV,oBAAK,OAAL,GAAe,MAAK,iBAAL,CAAuB,SAAS,OAAhC,CAAf;AACA,oBAAK,aAAL,GAAqB,IAArB;AACD,aATH;AAUD;;;0CAEc;AACb,iBAAK,OAAL,GAAe,EAAf;AACD;;;4CAEiB,O,EAAQ;AACxB,gBAAI,MAAM,EAAV;AACA,cAAE,IAAF,CAAO,OAAP,EAAgB,UAAS,KAAT,EAAgB,GAAhB,EAAoB;AAClC,kBAAI,SAAS,EAAb;AACA,qBAAO,MAAP,GAAgB,MAAM,MAAtB;AACA,qBAAO,IAAP,GAAc,EAAd;AACA,gBAAE,GAAF,CAAM,MAAM,IAAZ,EAAkB,UAAS,GAAT,EAAc,GAAd,EAAkB;AAC1C;AACA;AACA;AACA;AACQ,oBAAI,OAAO,EAAE,SAAF,CAAY,OAAO,IAAnB,EAAyB,EAAC,QAAQ,IAAI,GAAb,EAAzB,CAAX;AACA,oBAAI,OAAJ;AACA,oBAAI,QAAQ,CAAC,CAAb,EAAgB;AACd,4BAAU,EAAC,MAAM,EAAP,EAAW,QAAQ,EAAnB,EAAV;AACD,iBAFD,MAEO;AACL,4BAAU,OAAO,IAAP,CAAY,IAAZ,CAAV;AACD;AACD,wBAAQ,MAAR,CAAe,IAAf,CAAoB,IAAI,KAAxB;AACA,wBAAQ,MAAR,GAAiB,EAAE,MAAF,CAAS,EAAE,IAAF,CAAO,QAAQ,MAAf,CAAT,CAAjB;AACA,wBAAQ,IAAR,GAAe,IAAI,GAAnB;AACA,oBAAI,OAAO,CAAC,CAAZ,EAAe;AACb,yBAAO,IAAP,CAAY,IAAZ,IAAoB,OAApB;AACD,iBAFD,MAEO;AACL,yBAAO,IAAP,CAAY,IAAZ,CAAiB,OAAjB;AACD;AACF,eApBiB,CAoBhB,IApBgB,CAoBX,MApBW,CAAlB;AAqBA,qBAAO,IAAP,GAAc,EAAE,MAAF,CAAS,OAAO,IAAhB,EAAsB,MAAtB,CAAd;AACA,kBAAI,IAAJ,CAAS,MAAT;AACD,aA3Be,CA2Bd,IA3Bc,CA2BT,GA3BS,CAAhB;;AA6BA,mBAAO,EAAE,MAAF,CAAS,GAAT,EAAc,QAAd,CAAP;AACD;;;kCAEO,Q,EAAS;AACf,mBAAO,aAAW,SAAS,UAApB,GAA+B,GAA/B,GAAmC,SAAS,SAA5C,GAAsD,cAA7D;AACD;;;;;;;;AAGH,sBAAgB,WAAhB,GAA8B,yBAA9B","file":"metrics.js","sourcesContent":["\nimport _ from 'lodash';\nimport {AuthsPageCtrl} from 'plugins/timely-app/components/authspage';\n\nexport class MetricsPageCtrl {\n\n constructor(backendSrv, datasourceSrv){\n this.hasDatasource = false;\n this.backendSrv = backendSrv;\n var apc = new AuthsPageCtrl(backendSrv, datasourceSrv);\n this.datasources = apc.getTimelyDatasources();\n if( this.datasources.length > 0){\n this.source = this.datasources[0];\n this.updateDatasource();\n }\n }\n\n updateDatasource(){\n this._clearMetrics();\n var uri = this._getURI(this.source.jsonData);\n this.backendSrv.request(\n { method: 'GET',\n url: uri,\n withCredentials: true,\n params: \"\" }\n ).then(\n response => {\n this.metrics = this._transformMetrics(response.metrics);\n this.hasDatasource = true;\n });\n }\n\n _clearMetrics(){\n this.metrics = [];\n }\n\n _transformMetrics(metrics){\n var ret = [];\n _.each(metrics, function(value, idx){\n var metric = {};\n metric.metric = value.metric;\n metric.tags = [];\n _.map(value.tags, function(tag, key){\n// result || (result = {});\n// result['name'] = tag.key;\n// result['values'] || (result['values'] = []);\n// result['values'].push(tag.value);\n var gIdx = _.findIndex(metric.tags, {'name': tag.key});\n var grouped;\n if( gIdx == -1 ){\n grouped = {name: '', values: []};\n } else {\n grouped = metric.tags[gIdx];\n }\n grouped.values.push(tag.value);\n grouped.values = _.sortBy(_.uniq(grouped.values))\n grouped.name = tag.key;\n if( gIdx > -1 ){\n metric.tags[gIdx] = grouped;\n } else {\n metric.tags.push(grouped);\n }\n }.bind(metric));\n metric.tags = _.sortBy(metric.tags, 'name')\n ret.push(metric);\n }.bind(ret));\n\n return _.sortBy(ret, 'metric');\n }\n\n _getURI(jsonData){\n return 'https://'+jsonData.timelyHost+':'+jsonData.httpsPort+'/api/metrics'\n }\n\n}\nMetricsPageCtrl.templateUrl = 'components/metrics.html';\n"]}
+257
View File
@@ -0,0 +1,257 @@
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline">
<div class="gf-form max-width-25">
<label class="gf-form-label query-keyword width-8">
Metric
<label class="gf-form-label" bs-tooltip="ctrl.errors.metric" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.metric">
<i class="fa fa-warning"></i>
</label>
</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.metric"
spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()">
</input>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">
Aggregator
<a bs-tooltip="ctrl.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.aggregator">
<i class="fa fa-warning"></i>
</a>
</label>
<div class="gf-form-select-wrapper max-width-15">
<select ng-model="ctrl.target.aggregator" class="gf-form-input"
ng-options="agg for agg in ctrl.aggregators"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-6">
Alias:
<info-popover mode="right-normal">
Use patterns like $tag_tagname to replace part of the alias for a tag value
</info-popover>
</label>
<input type="text" class="gf-form-input"
ng-model="ctrl.target.alias"
spellcheck='false'
placeholder="series alias"
data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()"></input>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-25">
<label class="gf-form-label query-keyword width-8">Down sample</label>
<input type="text" class="gf-form-input"
ng-model="ctrl.target.downsampleInterval"
ng-model-onblur
ng-change="ctrl.targetBlur()"
placeholder="interval"></input>
<info-popover mode="right-absolute">
blank for auto, or for example <code>1m</code>
</info-popover>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">Aggregator</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.downsampleAggregator" class="gf-form-input"
ng-options="agg for agg in ctrl.aggregators"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<div class="gf-form" ng-if="ctrl.tsdbVersion >= 2">
<label class="gf-form-label query-keyword width-6">Fill</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.downsampleFillPolicy" class="gf-form-input"
ng-options="agg for agg in ctrl.fillPolicies"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<gf-form-switch class="gf-form"
label="Disable downsampling"
label-class="query-keyword"
checked="ctrl.target.disableDownsampling"
on-change="ctrl.targetBlur()">
</gf-form-switch>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.tsdbVersion >= 2">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8">
Filters
<info-popover mode="right-normal">
Filters does not work with tags, either of the two will work but not both.
</info-popover>
</label>
<div ng-repeat="fil in ctrl.target.filters track by $index" class="gf-form-label">
{{fil.tagk}}&nbsp;=&nbsp;{{fil.type}}&#40;{{fil.filter}}&#41;&nbsp;&#44&nbsp;groupBy&nbsp;=&nbsp;{{fil.groupBy}}
<a ng-click="ctrl.editFilter(fil, $index)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeFilter($index)">
<i class="fa fa-remove"></i>
</a>
</div>
<label class="gf-form-label query-keyword" ng-hide="ctrl.addFilterMode">
<a ng-click="ctrl.addFilter()">
<i class="fa fa-plus"></i>
</a>
</label>
</div>
<div class="gf-form-inline" ng-show="ctrl.addFilterMode">
<div class="gf-form">
<input type="text" class="gf-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentFilterKey" placeholder="key">
</input>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">Type</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.currentFilterType" class="gf-form-input" ng-options="filType for filType in ctrl.filterTypes">
</select>
</div>
</div>
<div class="gf-form">
<input type="text" class="gf-form-input" spellcheck='false' bs-typeahead="ctrl.suggestTagValues" data-min-length=0 data-items=100 ng-model="ctrl.target.currentFilterValue" placeholder="filter">
</input>
</div>
<gf-form-switch class="gf-form"
label="Group by"
label-class="query-keyword"
checked="ctrl.target.currentFilterGroupBy"
on-change="ctrl.targetBlur()">
</gf-form-switch>
<div class="gf-form" ng-show="ctrl.addFilterMode">
<label class="gf-form-label" ng-show="ctrl.errors.filters">
<a bs-tooltip="ctrl.errors.filters" style="color: rgb(229, 189, 28)" >
<i class="fa fa-warning"></i>
</a>
</label>
<label class="gf-form-label">
<a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">add filter</a>
<a ng-click="ctrl.closeAddFilterMode()">
<i class="fa fa-remove"></i>
</a>
</label>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8">
Tags
<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion >= 2">
Please use filters, tags are deprecated in opentsdb 2.2
</info-popover>
</label>
</div>
<div class="gf-form" ng-repeat="(key, value) in ctrl.target.tags track by $index" class="gf-form">
<label class="gf-form-label">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="ctrl.editTag(key, value)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeTag(key)">
<i class="fa fa-remove"></i>
</a>
</label>
</div>
<div class="gf-form" ng-hide="ctrl.addTagMode">
<label class="gf-form-label query-keyword">
<a ng-click="ctrl.addTag()"><i class="fa fa-plus"></i></a>
</label>
</div>
<div class="gf-form" ng-show="ctrl.addTagMode">
<input type="text"
class="gf-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentTagKey" placeholder="key">
</input>
<input type="text" class="gf-form-input"
spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value">
</input>
<label class="gf-form-label" ng-show="ctrl.errors.tags">
<a bs-tooltip="ctrl.errors.tags" style="color: rgb(229, 189, 28)" >
<i class="fa fa-warning"></i>
</a>
</label>
<label class="gf-form-label" >
<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags">add tag</a>
<a ng-click="ctrl.closeAddTagMode()"><i class="fa fa-remove"></i></a>
</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<gf-form-switch class="gf-form" label="Rate" label-class="width-8 query-keyword" checked="ctrl.target.shouldComputeRate" on-change="ctrl.targetBlur()">
</gf-form-switch>
<gf-form-switch ng-hide="!ctrl.target.shouldComputeRate"
class="gf-form" label="Counter" label-class="query-keyword" checked="ctrl.target.isCounter" on-change="ctrl.targetBlur()">
</gf-form-switch>
<div class="gf-form" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
<label class="gf-form-label query-keyword">Counter Max</label>
<input type="text" class="gf-form-input"
ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterMax" spellcheck='false'
placeholder="max value" ng-model-onblur
ng-blur="ctrl.targetBlur()">
</input>
<label class="gf-form-label query-keyword">Reset Value</label>
<input type="text" class="tight-form-input input-small"
ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterResetValue" spellcheck='false'
placeholder="reset value" ng-model-onblur
ng-blur="ctrl.targetBlur()">
</input>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+3
View File
@@ -0,0 +1,3 @@
.timely-app-heading {
text-align: center;
}
+35
View File
@@ -0,0 +1,35 @@
'use strict';
System.register(['lodash'], function (_export, _context) {
"use strict";
var _, DatasourceConfigCtrl;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
return {
setters: [function (_lodash) {
_ = _lodash.default;
}],
execute: function () {
_export('DatasourceConfigCtrl', DatasourceConfigCtrl = function DatasourceConfigCtrl() {
_classCallCheck(this, DatasourceConfigCtrl);
this.current.jsonData = this.current.jsonData || {};
this.current.access = 'direct';
this.current.basicAuth = false;
this.current.withCredentials = true;
this.current.suggestHost = 'localhost';
});
_export('DatasourceConfigCtrl', DatasourceConfigCtrl);
DatasourceConfigCtrl.templateUrl = 'datasource/partials/config.html';
}
};
});
//# sourceMappingURL=config.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../src/datasource/config.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAO,O;;;sCAGM,oB,GAEX,gCAAc;AAAA;;AACZ,aAAK,OAAL,CAAa,QAAb,GAAwB,KAAK,OAAL,CAAa,QAAb,IAAyB,EAAjD;AACA,aAAK,OAAL,CAAa,MAAb,GAAsB,QAAtB;AACA,aAAK,OAAL,CAAa,SAAb,GAAyB,KAAzB;AACA,aAAK,OAAL,CAAa,eAAb,GAA+B,IAA/B;AACA,aAAK,OAAL,CAAa,WAAb,GAA2B,WAA3B;AACD,O;;;;AAEH,2BAAqB,WAArB,GAAmC,iCAAnC","file":"config.js","sourcesContent":["import _ from 'lodash';\n\n\nexport class DatasourceConfigCtrl {\n\n constructor() {\n this.current.jsonData = this.current.jsonData || {};\n this.current.access = 'direct';\n this.current.basicAuth = false;\n this.current.withCredentials = true;\n this.current.suggestHost = 'localhost';\n }\n}\nDatasourceConfigCtrl.templateUrl = 'datasource/partials/config.html';\n"]}
@@ -0,0 +1,434 @@
{
"__inputs": [
{
"name": "DS_NAME",
"type": "datasource",
"pluginId": "timely-datasource"
}
],
"id": null,
"title": "${DS_NAME} - Status",
"originalTitle": "${DS_NAME} - Status",
"tags": ["imported"],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)",
"thresholdLine": false
},
"id": 1,
"legend": {
"alignAsTable": true,
"avg": false,
"current": true,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": true,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"isCounter": false,
"metric": "timely.metrics.received",
"refId": "A",
"shouldComputeRate": false
},
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"metric": "timely.keys.meta.inserted",
"refId": "B"
},
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"metric": "timely.keys.metric.inserted",
"refId": "C"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Timely Status",
"tooltip": {
"msResolution": true,
"shared": false,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": "Keys",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"title": "Row"
},
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 2,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "$tag_host",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {
"host": ".*"
}
}
],
"timeFrom": null,
"timeShift": null,
"title": "CPU User by Host",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 3,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "$tag_rack",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {
"rack": ".*"
}
}
],
"timeFrom": null,
"timeShift": null,
"title": "CPU User by Rack",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 4,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {}
}
],
"timeFrom": null,
"timeShift": null,
"title": "Cumulative CPU",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
}
],
"title": "New row"
}
],
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"refresh": "5s",
"schemaVersion": 12,
"version": 1,
"links": []
}
+533
View File
@@ -0,0 +1,533 @@
'use strict';
System.register(['lodash', 'angular', '../../../app/core/utils/datemath'], function (_export, _context) {
"use strict";
var _, angular, dateMath, _createClass, TimelyDatasource;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
return {
setters: [function (_lodash) {
_ = _lodash.default;
}, function (_angular) {
angular = _angular.default;
}, function (_appCoreUtilsDatemath) {
dateMath = _appCoreUtilsDatemath;
}],
execute: function () {
_createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
_export('TimelyDatasource', TimelyDatasource = function () {
// arguments injected by angular
function TimelyDatasource(instanceSettings, $q, backendSrv, templateSrv, contextSrv) {
_classCallCheck(this, TimelyDatasource);
this.type = instanceSettings.type;
this.name = instanceSettings.name;
this.jsonData = instanceSettings.jsonData || {};
this.httpsPort = instanceSettings.jsonData.httpsPort;
this.timelyHost = instanceSettings.jsonData.timelyHost;
this.wsPort = instanceSettings.jsonData.wsPort;
if (!instanceSettings.jsonData.basicAuths) {
instanceSettings.jsonData.basicAuths = false;
}
this.url = this._makeHttpsUrl();
this.withCredentials = true;
this.basicAuth = false;
this.supportMetrics = true;
this.tagKeys = {};
this.$q = $q;
this.backendSrv = backendSrv;
this.templateSrv = templateSrv;
this.contextSrv = contextSrv;
this.aggregatorsPromise = null;
this.filterTypesPromise = null;
}
// REQUIRED INTERFACE
_createClass(TimelyDatasource, [{
key: 'query',
value: function query(options) {
var start = this.convertToTSDBTime(options.rangeRaw.from, false);
var end = this.convertToTSDBTime(options.rangeRaw.to, true);
var qs = [];
_.each(options.targets, function (target) {
if (!target.metric) {
return;
}
qs.push(this.convertTargetToQuery(target, options));
}.bind(this));
var queries = _.compact(qs);
// No valid targets, return the empty result to save a round trip.
if (_.isEmpty(queries)) {
var d = this.$q.defer();
d.resolve({ data: [] });
return d.promise;
}
var groupByTags = {};
_.each(queries, function (query) {
if (query.filters && query.filters.length > 0) {
_.each(query.filters, function (val) {
groupByTags[val.tagk] = true;
});
} else {
_.each(query.tags, function (val, key) {
groupByTags[key] = true;
});
}
});
return this.performTimeSeriesQuery(queries, start, end).then(function (response) {
var metricToTargetMapping = this.mapMetricsToTargets(response.data, options);
var result = _.map(response.data, function (metricData, index) {
index = metricToTargetMapping[index];
if (index === -1) {
index = 0;
}
this._saveTagKeys(metricData);
return this.transformMetricData(metricData, groupByTags, options.targets[index], options);
}.bind(this));
return { data: result };
}.bind(this));
}
}, {
key: 'testDatasource',
value: function testDatasource() {
return this.backendSrv.datasourceRequest({
url: this.url + '/version',
method: 'GET'
}).then(function (response) {
if (response.status === 200) {
return { status: "success", message: "Data source is working", title: "Success" };
}
});
}
}, {
key: 'annotationQuery',
value: function annotationQuery(options) {
var start = convertToTSDBTime(options.rangeRaw.from, false);
var end = convertToTSDBTime(options.rangeRaw.to, true);
var qs = [];
var eventList = [];
qs.push({ aggregator: "sum", metric: options.annotation.target });
var queries = _.compact(qs);
return this.performTimeSeriesQuery(queries, start, end).then(function (results) {
if (results.data[0]) {
var annotationObject = results.data[0].annotations;
if (options.annotation.isGlobal) {
annotationObject = results.data[0].globalAnnotations;
}
if (annotationObject) {
_.each(annotationObject, function (annotation) {
var event = {
title: annotation.description,
time: Math.floor(annotation.startTime) * 1000,
text: annotation.notes,
annotation: options.annotation
};
eventList.push(event);
});
}
}
return eventList;
}.bind(this));
}
}, {
key: 'metricFindQuery',
value: function metricFindQuery(query) {
if (!query) {
return this.$q.when([]);
}
var interpolated;
try {
interpolated = this.templateSrv.replace(query);
} catch (err) {
return this.$q.reject(err);
}
var responseTransform = function responseTransform(result) {
return _.map(result, function (value) {
return { text: value };
});
};
var metrics_regex = /metrics\((.*)\)/;
var tag_names_regex = /tag_names\((.*)\)/;
var tag_values_regex = /tag_values\((.*?),\s?(.*)\)/;
var tag_names_suggest_regex = /suggest_tagk\((.*)\)/;
var tag_values_suggest_regex = /suggest_tagv\((.*)\)/;
var metrics_query = interpolated.match(metrics_regex);
if (metrics_query) {
return this._performSuggestQuery(metrics_query[1], 'metrics').then(responseTransform);
}
var tag_names_query = interpolated.match(tag_names_regex);
if (tag_names_query) {
return this._performMetricKeyLookup(tag_names_query[1]).then(responseTransform);
}
var tag_values_query = interpolated.match(tag_values_regex);
if (tag_values_query) {
return this._performMetricKeyValueLookup(tag_values_query[1], tag_values_query[2]).then(responseTransform);
}
var tag_names_suggest_query = interpolated.match(tag_names_suggest_regex);
if (tag_names_suggest_query) {
return this._performSuggestQuery(tag_names_suggest_query[1], 'tagk').then(responseTransform);
}
var tag_values_suggest_query = interpolated.match(tag_values_suggest_regex);
if (tag_values_suggest_query) {
return this._performSuggestQuery(tag_values_suggest_query[1], 'tagv').then(responseTransform);
}
return this.$q.when([]);
}
}, {
key: 'performTimeSeriesQuery',
value: function performTimeSeriesQuery(queries, start, end) {
var msResolution = true;
var reqBody = {
start: start,
queries: queries,
msResolution: msResolution,
globalAnnotations: true,
showQuery: true
};
// Relative queries (e.g. last hour) don't include an end time
if (end) {
reqBody.end = end;
}
var options = {
method: 'POST',
url: this.url + '/api/query',
data: reqBody
};
this._addCredentialOptions(options);
// In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests
// go as POST rather than OPTIONS+POST
options.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
return this.backendSrv.datasourceRequest(options);
}
}, {
key: '_saveTagKeys',
value: function _saveTagKeys(metricData) {
var tagKeys = Object.keys(metricData.tags);
_.each(metricData.aggregateTags, function (tag) {
tagKeys.push(tag);
});
this.tagKeys[metricData.metric] = tagKeys;
}
}, {
key: 'suggestTagKeys',
value: function suggestTagKeys(metric) {
return this.$q.when(this.tagKeys[metric] || []);
}
}, {
key: '_performSuggestQuery',
value: function _performSuggestQuery(query, type) {
return this._get('/api/suggest', { type: type, q: query, max: 1000 }).then(function (result) {
return result.data;
});
}
}, {
key: '_performMetricKeyValueLookup',
value: function _performMetricKeyValueLookup(metric, keys) {
if (!metric || !keys) {
return $q.when([]);
}
var keysArray = keys.split(",").map(function (key) {
return key.trim();
});
var key = keysArray[0];
var keysQuery = key + "=*";
if (keysArray.length > 1) {
keysQuery += "," + keysArray.splice(1).join(",");
}
var m = metric + "{" + keysQuery + "}";
return this._get('/api/search/lookup', { m: m, limit: 3000 }).then(function (result) {
result = result.data.results;
var tagvs = [];
_.each(result, function (r) {
if (tagvs.indexOf(r.tags[key]) === -1) {
tagvs.push(r.tags[key]);
}
});
return tagvs;
});
}
}, {
key: '_performMetricKeyLookup',
value: function _performMetricKeyLookup(metric) {
if (!metric) {
return this.$q.when([]);
}
return this._get('/api/search/lookup', { m: metric, limit: 1000 }).then(function (result) {
result = result.data.results;
var tagks = [];
_.each(result, function (r) {
_.each(r.tags, function (tagv, tagk) {
if (tagks.indexOf(tagk) === -1) {
tagks.push(tagk);
}
});
});
return tagks;
});
}
}, {
key: '_get',
value: function _get(relativeUrl, params) {
var options = {
method: 'GET',
url: this.url + relativeUrl,
params: params
};
this._addCredentialOptions(options);
return this.backendSrv.datasourceRequest(options);
}
}, {
key: '_addCredentialOptions',
value: function _addCredentialOptions(options) {
if (this.basicAuth || this.withCredentials) {
options.withCredentials = true;
}
if (this.basicAuth) {
options.headers = { "Authorization": this.basicAuth };
}
}
}, {
key: 'getAggregators',
value: function getAggregators() {
if (this.aggregatorsPromise) {
return this.aggregatorsPromise;
}
this.aggregatorsPromise = this._get('/api/aggregators').then(function (result) {
if (result.data && _.isArray(result.data)) {
return result.data.sort();
}
return [];
});
return this.aggregatorsPromise;
}
}, {
key: 'getFilterTypes',
value: function getFilterTypes() {
if (this.filterTypesPromise) {
return this.filterTypesPromise;
}
// filters are for a newer opentsdb api version than Timely originally copied.
this.filterTypesPromise = this._get('/api/aggregators').then(function (result) {
return [];
});
return this.filterTypesPromise;
}
}, {
key: 'transformMetricData',
value: function transformMetricData(md, groupByTags, target, options) {
var metricLabel = this.createMetricLabel(md, target, groupByTags, options);
var dps = [];
// TSDB returns datapoints has a hash of ts => value.
// Can't use _.pairs(invert()) because it stringifies keys/values
_.each(md.dps, function (v, k) {
dps.push([v, k * 1]);
});
return { target: metricLabel, datapoints: dps };
}
}, {
key: 'createMetricLabel',
value: function createMetricLabel(md, target, groupByTags, options) {
if (target.alias) {
var scopedVars = _.clone(options.scopedVars || {});
_.each(md.tags, function (value, key) {
scopedVars['tag_' + key] = { value: value };
});
return this.templateSrv.replace(target.alias, scopedVars);
}
var label = md.metric;
var tagData = [];
if (!_.isEmpty(md.tags)) {
_.each(_.pairs(md.tags), function (tag) {
if (_.has(groupByTags, tag[0])) {
tagData.push(tag[0] + "=" + tag[1]);
}
});
}
if (!_.isEmpty(tagData)) {
label += "{" + tagData.join(", ") + "}";
}
return label;
}
}, {
key: 'convertTargetToQuery',
value: function convertTargetToQuery(target, options) {
if (!target.metric || target.hide) {
return null;
}
var query = {
metric: this.templateSrv.replace(target.metric, options.scopedVars),
aggregator: "avg"
};
if (target.aggregator) {
query.aggregator = this.templateSrv.replace(target.aggregator);
}
if (target.shouldComputeRate) {
query.rate = true;
query.rateOptions = {
counter: !!target.isCounter
};
if (target.counterMax && target.counterMax.length) {
query.rateOptions.counterMax = parseInt(target.counterMax);
}
if (target.counterResetValue && target.counterResetValue.length) {
query.rateOptions.resetValue = parseInt(target.counterResetValue);
}
}
if (!target.disableDownsampling) {
var interval = this.templateSrv.replace(target.downsampleInterval || options.interval);
if (interval.match(/\.[0-9]+s/)) {
interval = parseFloat(interval) * 1000 + "ms";
}
query.downsample = interval + "-" + target.downsampleAggregator;
if (target.downsampleFillPolicy && target.downsampleFillPolicy !== "none") {
query.downsample += "-" + target.downsampleFillPolicy;
}
}
if (target.filters && target.filters.length > 0) {
query.filters = angular.copy(target.filters);
if (query.filters) {
for (var filter_key in query.filters) {
query.filters[filter_key].filter = this.templateSrv.replace(query.filters[filter_key].filter, options.scopedVars, 'pipe');
}
}
} else {
query.tags = angular.copy(target.tags);
if (query.tags) {
for (var tag_key in query.tags) {
query.tags[tag_key] = this.templateSrv.replace(query.tags[tag_key], options.scopedVars, 'pipe');
}
}
}
return query;
}
}, {
key: 'mapMetricsToTargets',
value: function mapMetricsToTargets(metrics, options) {
var interpolatedTagValue;
return _.map(metrics, function (metricData) {
return _.findIndex(options.targets, function (target) {
if (target.filters && target.filters.length > 0) {
return target.metric === metricData.metric;
} else {
return target.metric === metricData.metric && _.all(target.tags, function (tagV, tagK) {
interpolatedTagValue = this.templateSrv.replace(tagV, options.scopedVars, 'pipe');
return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*";
}.bind(this));
}
}.bind(this));
}.bind(this));
}
}, {
key: 'convertToTSDBTime',
value: function convertToTSDBTime(date, roundUp) {
if (date === 'now') {
return null;
}
date = dateMath.parse(date, roundUp);
return date.valueOf();
}
}, {
key: '_makeHttpsUrl',
value: function _makeHttpsUrl() {
return 'https://' + this.timelyHost + ':' + this.httpsPort;
}
}, {
key: '_makeWsUrl',
value: function _makeWsUrl() {
return 'ws://' + this.timelyHost + ':' + this.wsPort;
}
}]);
return TimelyDatasource;
}());
_export('TimelyDatasource', TimelyDatasource);
}
};
});
//# sourceMappingURL=datasource.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../src/datasource/datasource.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAO,O;;AACA,a;;AACK,c;;;;;;;;;;;;;;;;;;;;;kCAEC,gB;;AAEX;AACA,kCAAY,gBAAZ,EAA8B,EAA9B,EAAkC,UAAlC,EAA8C,WAA9C,EAA2D,UAA3D,EAAuE;AAAA;;AACrE,eAAK,IAAL,GAAY,iBAAiB,IAA7B;AACA,eAAK,IAAL,GAAY,iBAAiB,IAA7B;;AAEA,eAAK,QAAL,GAAgB,iBAAiB,QAAjB,IAA6B,EAA7C;AACA,eAAK,SAAL,GAAiB,iBAAiB,QAAjB,CAA0B,SAA3C;AACA,eAAK,UAAL,GAAkB,iBAAiB,QAAjB,CAA0B,UAA5C;AACA,eAAK,MAAL,GAAc,iBAAiB,QAAjB,CAA0B,MAAxC;AACA,cAAI,CAAC,iBAAiB,QAAjB,CAA0B,UAA/B,EAA0C;AACxC,6BAAiB,QAAjB,CAA0B,UAA1B,GAAuC,KAAvC;AACD;;AAED,eAAK,GAAL,GAAW,KAAK,aAAL,EAAX;AACA,eAAK,eAAL,GAAuB,IAAvB;AACA,eAAK,SAAL,GAAiB,KAAjB;;AAEA,eAAK,cAAL,GAAsB,IAAtB;AACA,eAAK,OAAL,GAAe,EAAf;;AAEA,eAAK,EAAL,GAAU,EAAV;AACA,eAAK,UAAL,GAAkB,UAAlB;AACA,eAAK,WAAL,GAAmB,WAAnB;AACA,eAAK,UAAL,GAAkB,UAAlB;;AAEA,eAAK,kBAAL,GAA0B,IAA1B;AACA,eAAK,kBAAL,GAA0B,IAA1B;AAED;;AAED;;;;;gCACM,O,EAAQ;AACZ,gBAAI,QAAQ,KAAK,iBAAL,CAAuB,QAAQ,QAAR,CAAiB,IAAxC,EAA8C,KAA9C,CAAZ;AACA,gBAAI,MAAM,KAAK,iBAAL,CAAuB,QAAQ,QAAR,CAAiB,EAAxC,EAA4C,IAA5C,CAAV;AACA,gBAAI,KAAK,EAAT;;AAEA,cAAE,IAAF,CAAO,QAAQ,OAAf,EAAwB,UAAS,MAAT,EAAiB;AACvC,kBAAI,CAAC,OAAO,MAAZ,EAAoB;AAAE;AAAS;AAC/B,iBAAG,IAAH,CAAQ,KAAK,oBAAL,CAA0B,MAA1B,EAAkC,OAAlC,CAAR;AACD,aAHuB,CAGtB,IAHsB,CAGjB,IAHiB,CAAxB;;AAKA,gBAAI,UAAU,EAAE,OAAF,CAAU,EAAV,CAAd;;AAEA;AACA,gBAAI,EAAE,OAAF,CAAU,OAAV,CAAJ,EAAwB;AACtB,kBAAI,IAAI,KAAK,EAAL,CAAQ,KAAR,EAAR;AACA,gBAAE,OAAF,CAAU,EAAE,MAAM,EAAR,EAAV;AACA,qBAAO,EAAE,OAAT;AACD;;AAED,gBAAI,cAAc,EAAlB;AACA,cAAE,IAAF,CAAO,OAAP,EAAgB,UAAS,KAAT,EAAgB;AAC9B,kBAAI,MAAM,OAAN,IAAiB,MAAM,OAAN,CAAc,MAAd,GAAuB,CAA5C,EAA+C;AAC7C,kBAAE,IAAF,CAAO,MAAM,OAAb,EAAsB,UAAS,GAAT,EAAc;AAClC,8BAAY,IAAI,IAAhB,IAAwB,IAAxB;AACD,iBAFD;AAGD,eAJD,MAIO;AACL,kBAAE,IAAF,CAAO,MAAM,IAAb,EAAmB,UAAS,GAAT,EAAc,GAAd,EAAmB;AACpC,8BAAY,GAAZ,IAAmB,IAAnB;AACD,iBAFD;AAGD;AACF,aAVD;;AAYA,mBAAO,KAAK,sBAAL,CAA4B,OAA5B,EAAqC,KAArC,EAA4C,GAA5C,EAAiD,IAAjD,CAAsD,UAAS,QAAT,EAAmB;AAC9E,kBAAI,wBAAwB,KAAK,mBAAL,CAAyB,SAAS,IAAlC,EAAwC,OAAxC,CAA5B;AACA,kBAAI,SAAS,EAAE,GAAF,CAAM,SAAS,IAAf,EAAqB,UAAS,UAAT,EAAqB,KAArB,EAA4B;AAC5D,wBAAQ,sBAAsB,KAAtB,CAAR;AACA,oBAAI,UAAU,CAAC,CAAf,EAAkB;AAChB,0BAAQ,CAAR;AACD;AACD,qBAAK,YAAL,CAAkB,UAAlB;;AAEA,uBAAO,KAAK,mBAAL,CAAyB,UAAzB,EAAqC,WAArC,EAAkD,QAAQ,OAAR,CAAgB,KAAhB,CAAlD,EAA0E,OAA1E,CAAP;AACD,eARiC,CAQhC,IARgC,CAQ3B,IAR2B,CAArB,CAAb;AASA,qBAAO,EAAE,MAAM,MAAR,EAAP;AACD,aAZ4D,CAY3D,IAZ2D,CAYtD,IAZsD,CAAtD,CAAP;AAaD;;;2CAGe;AACd,mBAAO,KAAK,UAAL,CAAgB,iBAAhB,CAAkC;AACvC,mBAAK,KAAK,GAAL,GAAW,UADuB;AAEvC,sBAAQ;AAF+B,aAAlC,EAGJ,IAHI,CAGC,oBAAY;AAClB,kBAAI,SAAS,MAAT,KAAoB,GAAxB,EAA6B;AAC3B,uBAAO,EAAE,QAAQ,SAAV,EAAqB,SAAS,wBAA9B,EAAwD,OAAO,SAA/D,EAAP;AACD;AACF,aAPM,CAAP;AAQD;;;0CAGe,O,EAAQ;AACtB,gBAAI,QAAQ,kBAAkB,QAAQ,QAAR,CAAiB,IAAnC,EAAyC,KAAzC,CAAZ;AACA,gBAAI,MAAM,kBAAkB,QAAQ,QAAR,CAAiB,EAAnC,EAAuC,IAAvC,CAAV;AACA,gBAAI,KAAK,EAAT;AACA,gBAAI,YAAY,EAAhB;;AAEA,eAAG,IAAH,CAAQ,EAAE,YAAW,KAAb,EAAoB,QAAO,QAAQ,UAAR,CAAmB,MAA9C,EAAR;;AAEA,gBAAI,UAAU,EAAE,OAAF,CAAU,EAAV,CAAd;;AAEA,mBAAO,KAAK,sBAAL,CAA4B,OAA5B,EAAqC,KAArC,EAA4C,GAA5C,EAAiD,IAAjD,CAAsD,UAAS,OAAT,EAAkB;AAC7E,kBAAG,QAAQ,IAAR,CAAa,CAAb,CAAH,EAAoB;AAClB,oBAAI,mBAAmB,QAAQ,IAAR,CAAa,CAAb,EAAgB,WAAvC;AACA,oBAAG,QAAQ,UAAR,CAAmB,QAAtB,EAA+B;AAC7B,qCAAmB,QAAQ,IAAR,CAAa,CAAb,EAAgB,iBAAnC;AACD;AACD,oBAAG,gBAAH,EAAqB;AACnB,oBAAE,IAAF,CAAO,gBAAP,EAAyB,UAAS,UAAT,EAAqB;AAC5C,wBAAI,QAAQ;AACV,6BAAO,WAAW,WADR;AAEV,4BAAM,KAAK,KAAL,CAAW,WAAW,SAAtB,IAAmC,IAF/B;AAGV,4BAAM,WAAW,KAHP;AAIV,kCAAY,QAAQ;AAJV,qBAAZ;;AAOA,8BAAU,IAAV,CAAe,KAAf;AACD,mBATD;AAUD;AACF;AACD,qBAAO,SAAP;AAED,aArB4D,CAqB3D,IArB2D,CAqBtD,IArBsD,CAAtD,CAAP;AAsBD;;;0CAGe,K,EAAO;AACrB,gBAAI,CAAC,KAAL,EAAY;AAAE,qBAAO,KAAK,EAAL,CAAQ,IAAR,CAAa,EAAb,CAAP;AAA0B;;AAExC,gBAAI,YAAJ;AACA,gBAAI;AACF,6BAAe,KAAK,WAAL,CAAiB,OAAjB,CAAyB,KAAzB,CAAf;AACD,aAFD,CAGA,OAAO,GAAP,EAAY;AACV,qBAAO,KAAK,EAAL,CAAQ,MAAR,CAAe,GAAf,CAAP;AACD;;AAED,gBAAI,oBAAoB,SAApB,iBAAoB,CAAS,MAAT,EAAiB;AACvC,qBAAO,EAAE,GAAF,CAAM,MAAN,EAAc,UAAS,KAAT,EAAgB;AACnC,uBAAO,EAAC,MAAM,KAAP,EAAP;AACD,eAFM,CAAP;AAGD,aAJD;;AAMA,gBAAI,gBAAgB,iBAApB;AACA,gBAAI,kBAAkB,mBAAtB;AACA,gBAAI,mBAAmB,6BAAvB;AACA,gBAAI,0BAA0B,sBAA9B;AACA,gBAAI,2BAA2B,sBAA/B;;AAEA,gBAAI,gBAAgB,aAAa,KAAb,CAAmB,aAAnB,CAApB;AACA,gBAAI,aAAJ,EAAmB;AACjB,qBAAO,KAAK,oBAAL,CAA0B,cAAc,CAAd,CAA1B,EAA4C,SAA5C,EAAuD,IAAvD,CAA4D,iBAA5D,CAAP;AACD;;AAED,gBAAI,kBAAkB,aAAa,KAAb,CAAmB,eAAnB,CAAtB;AACA,gBAAI,eAAJ,EAAqB;AACnB,qBAAO,KAAK,uBAAL,CAA6B,gBAAgB,CAAhB,CAA7B,EAAiD,IAAjD,CAAsD,iBAAtD,CAAP;AACD;;AAED,gBAAI,mBAAmB,aAAa,KAAb,CAAmB,gBAAnB,CAAvB;AACA,gBAAI,gBAAJ,EAAsB;AACpB,qBAAO,KAAK,4BAAL,CAAkC,iBAAiB,CAAjB,CAAlC,EAAuD,iBAAiB,CAAjB,CAAvD,EAA4E,IAA5E,CAAiF,iBAAjF,CAAP;AACD;;AAED,gBAAI,0BAA0B,aAAa,KAAb,CAAmB,uBAAnB,CAA9B;AACA,gBAAI,uBAAJ,EAA6B;AAC3B,qBAAO,KAAK,oBAAL,CAA0B,wBAAwB,CAAxB,CAA1B,EAAsD,MAAtD,EAA8D,IAA9D,CAAmE,iBAAnE,CAAP;AACD;;AAED,gBAAI,2BAA2B,aAAa,KAAb,CAAmB,wBAAnB,CAA/B;AACA,gBAAI,wBAAJ,EAA8B;AAC5B,qBAAO,KAAK,oBAAL,CAA0B,yBAAyB,CAAzB,CAA1B,EAAuD,MAAvD,EAA+D,IAA/D,CAAoE,iBAApE,CAAP;AACD;;AAED,mBAAO,KAAK,EAAL,CAAQ,IAAR,CAAa,EAAb,CAAP;AACD;;;iDALine truncated
+39
View File
@@ -0,0 +1,39 @@
'use strict';
System.register(['./datasource', './query_ctrl', './config'], function (_export, _context) {
"use strict";
var TimelyDatasource, TimelyQueryCtrl, DatasourceConfigCtrl, TimelyAnnotationsQueryCtrl;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
return {
setters: [function (_datasource) {
TimelyDatasource = _datasource.TimelyDatasource;
}, function (_query_ctrl) {
TimelyQueryCtrl = _query_ctrl.TimelyQueryCtrl;
}, function (_config) {
DatasourceConfigCtrl = _config.DatasourceConfigCtrl;
}],
execute: function () {
_export('AnnotationsQueryCtrl', TimelyAnnotationsQueryCtrl = function TimelyAnnotationsQueryCtrl() {
_classCallCheck(this, TimelyAnnotationsQueryCtrl);
});
TimelyAnnotationsQueryCtrl.templateUrl = 'components/annotations.editor.html';
_export('Datasource', TimelyDatasource);
_export('QueryCtrl', TimelyQueryCtrl);
_export('ConfigCtrl', DatasourceConfigCtrl);
_export('AnnotationsQueryCtrl', TimelyAnnotationsQueryCtrl);
}
};
});
//# sourceMappingURL=module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../src/datasource/module.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAQ,sB,eAAA,gB;;AACA,qB,eAAA,e;;AACA,0B,WAAA,oB;;;sCAEF,0B;;;;AACN,iCAA2B,WAA3B,GAAyC,oCAAzC;;4BAGE,gB;;2BACA,e;;4BACA,oB;;sCACA,0B","file":"module.js","sourcesContent":["import {TimelyDatasource} from './datasource';\nimport {TimelyQueryCtrl} from './query_ctrl';\nimport {DatasourceConfigCtrl} from './config';\n\nclass TimelyAnnotationsQueryCtrl {}\nTimelyAnnotationsQueryCtrl.templateUrl = 'components/annotations.editor.html';\n\nexport {\n TimelyDatasource as Datasource,\n TimelyQueryCtrl as QueryCtrl,\n DatasourceConfigCtrl as ConfigCtrl,\n TimelyAnnotationsQueryCtrl as AnnotationsQueryCtrl\n};\n"]}
+66
View File
@@ -0,0 +1,66 @@
<div class="gf-form-group">
<div class="gf-form-group">
<h3 class="page-heading">Server Settings</h3>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-7">Hostname</span>
<input class="gf-form-input" type="text"
ng-model="ctrl.current.jsonData.timelyHost" placeholder="{{ctrl.current.jsonData.suggestHost}}"
min-length="0"
ng-pattern="/(\w+:{0,1}\w*@)?(\S+)?$/" required></input>
<info-popover mode="right-absolute">
<p>Hostname of timely server</p>
</info-popover>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-7">HTTPS Port</span>
<input class="gf-form-input" type="text"
ng-model="ctrl.current.jsonData.httpsPort"
min-length="0"
ng-pattern="/[0-9]+/" required></input>
<info-popover mode="right-absolute">
<p>Port number for Timely HTTPS access</p>
</info-popover>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-7">WS Port</span>
<input class="gf-form-input" type="text"
ng-model="ctrl.current.jsonData.wsPort"
min-length="0"
ng-pattern="/[0-9]+/"></input>
<info-popover mode="right-absolute">
<p>Port number for Timely WebSocket access</p>
</info-popover>
</div>
</div>
<gf-form-switch class="gf-form "
label="Basic Auths"
checked="ctrl.current.jsonData.basicAuths" switch-class="max-width-6">
</gf-form-switch>
</div>
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label">
<a ng-href="https://{{ctrl.current.jsonData.timelyHost}}:{{ctrl.current.jsonData.httpsPort}}/version" target="_blank">
Browser Cert Check
</a>
<info-popover mode="right">
<p>Opens a new browser to test https access, allows you to accept a cert, etc.</p>
</info-popover>
</span>
</div>
</div>
</div>
+24
View File
@@ -0,0 +1,24 @@
{
"type": "datasource",
"name": "Timely",
"id": "timely-datasource",
"metrics": true,
"defaultMatchFormat": "pipe",
"annotations": true,
"includes": [
{"type": "dashboard", "name": "Timely Metrics", "path": "dashboards/standalone_test.json"}
],
"info": {
"author": {
"name": "National Security Agency",
"url": "http://github.com/NationalSecurityAgency/timely"
},
"logos": {
"small": "../timely-app/img/brand_small.png",
"large": "../timely-app/img/brand_large.png"
}
}
}
+277
View File
@@ -0,0 +1,277 @@
'use strict';
System.register(['lodash', 'app/plugins/sdk'], function (_export, _context) {
"use strict";
var _, QueryCtrl, _createClass, TimelyQueryCtrl;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
return {
setters: [function (_lodash) {
_ = _lodash.default;
}, function (_appPluginsSdk) {
QueryCtrl = _appPluginsSdk.QueryCtrl;
}],
execute: function () {
_createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
_export('TimelyQueryCtrl', TimelyQueryCtrl = function (_QueryCtrl) {
_inherits(TimelyQueryCtrl, _QueryCtrl);
function TimelyQueryCtrl($scope, $injector, uiSegmentSrv) {
_classCallCheck(this, TimelyQueryCtrl);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(TimelyQueryCtrl).call(this, $scope, $injector));
_this.errors = _this.validateTarget();
_this.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax'];
_this.fillPolicies = ['none', 'nan', 'null', 'zero'];
_this.filterTypes = ['wildcard', 'iliteral_or', 'not_iliteral_or', 'not_literal_or', 'iwildcard', 'literal_or', 'regexp'];
if (!_this.target.aggregator) {
_this.target.aggregator = 'sum';
}
if (!_this.target.downsampleAggregator) {
_this.target.downsampleAggregator = 'avg';
}
if (!_this.target.downsampleFillPolicy) {
_this.target.downsampleFillPolicy = 'none';
}
_this.datasource.getAggregators().then(function (aggs) {
if (aggs.length !== 0) {
_this.aggregators = aggs;
}
});
_this.datasource.getFilterTypes().then(function (filterTypes) {
if (filterTypes.length !== 0) {
_this.filterTypes = filterTypes;
}
});
// needs to be defined here as it is called from typeahead
_this.suggestMetrics = function (query, callback) {
_this.datasource.metricFindQuery('metrics(' + query + ')').then(_this.getTextValues).then(callback);
};
_this.suggestTagKeys = function (query, callback) {
_this.datasource.suggestTagKeys(_this.target.metric).then(callback);
};
_this.suggestTagValues = function (query, callback) {
_this.datasource.metricFindQuery('suggest_tagv(' + query + ')').then(_this.getTextValues).then(callback);
};
return _this;
}
_createClass(TimelyQueryCtrl, [{
key: 'targetBlur',
value: function targetBlur() {
this.errors = this.validateTarget();
this.refresh();
}
}, {
key: 'getTextValues',
value: function getTextValues(metricFindResult) {
return _.map(metricFindResult, function (value) {
return value.text;
});
}
}, {
key: 'addTag',
value: function addTag() {
if (this.target.filters && this.target.filters.length > 0) {
this.errors.tags = "Please remove filters to use tags, tags and filters are mutually exclusive.";
}
if (!this.addTagMode) {
this.addTagMode = true;
return;
}
if (!this.target.tags) {
this.target.tags = {};
}
this.errors = this.validateTarget();
if (!this.errors.tags) {
this.target.tags[this.target.currentTagKey] = this.target.currentTagValue;
this.target.currentTagKey = '';
this.target.currentTagValue = '';
this.targetBlur();
}
this.addTagMode = false;
}
}, {
key: 'removeTag',
value: function removeTag(key) {
delete this.target.tags[key];
this.targetBlur();
}
}, {
key: 'editTag',
value: function editTag(key, value) {
this.removeTag(key);
this.target.currentTagKey = key;
this.target.currentTagValue = value;
this.addTag();
}
}, {
key: 'closeAddTagMode',
value: function closeAddTagMode() {
this.addTagMode = false;
return;
}
}, {
key: 'addFilter',
value: function addFilter() {
if (this.target.tags && _.size(this.target.tags) > 0) {
this.errors.filters = "Please remove tags to use filters, tags and filters are mutually exclusive.";
}
if (!this.addFilterMode) {
this.addFilterMode = true;
return;
}
if (!this.target.filters) {
this.target.filters = [];
}
if (!this.target.currentFilterType) {
this.target.currentFilterType = 'iliteral_or';
}
if (!this.target.currentFilterGroupBy) {
this.target.currentFilterGroupBy = false;
}
this.errors = this.validateTarget();
if (!this.errors.filters) {
var currentFilter = {
type: this.target.currentFilterType,
tagk: this.target.currentFilterKey,
filter: this.target.currentFilterValue,
groupBy: this.target.currentFilterGroupBy
};
this.target.filters.push(currentFilter);
this.target.currentFilterType = 'literal_or';
this.target.currentFilterKey = '';
this.target.currentFilterValue = '';
this.target.currentFilterGroupBy = false;
this.targetBlur();
}
this.addFilterMode = false;
}
}, {
key: 'removeFilter',
value: function removeFilter(index) {
this.target.filters.splice(index, 1);
this.targetBlur();
}
}, {
key: 'editFilter',
value: function editFilter(fil, index) {
this.removeFilter(index);
this.target.currentFilterKey = fil.tagk;
this.target.currentFilterValue = fil.filter;
this.target.currentFilterType = fil.type;
this.target.currentFilterGroupBy = fil.groupBy;
this.addFilter();
}
}, {
key: 'closeAddFilterMode',
value: function closeAddFilterMode() {
this.addFilterMode = false;
return;
}
}, {
key: 'validateTarget',
value: function validateTarget() {
var errs = {};
if (this.target.shouldDownsample) {
try {
if (this.target.downsampleInterval) {
kbn.describe_interval(this.target.downsampleInterval);
} else {
errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h').";
}
} catch (err) {
errs.downsampleInterval = err.message;
}
}
if (this.target.tags && _.has(this.target.tags, this.target.currentTagKey)) {
errs.tags = "Duplicate tag key '" + this.target.currentTagKey + "'.";
}
return errs;
}
}]);
return TimelyQueryCtrl;
}(QueryCtrl));
_export('TimelyQueryCtrl', TimelyQueryCtrl);
TimelyQueryCtrl.templateUrl = 'components/query.editor.html';
}
};
});
//# sourceMappingURL=query_ctrl.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../src/datasource/query_ctrl.js"],"names":[],"mappings":"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","file":"query_ctrl.js","sourcesContent":["import _ from 'lodash';\nimport {QueryCtrl} from 'app/plugins/sdk';\n\nexport class TimelyQueryCtrl extends QueryCtrl {\n\n constructor($scope, $injector, uiSegmentSrv) {\n super($scope, $injector);\n\n this.errors = this.validateTarget();\n this.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax'];\n Line truncated
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+24
View File
@@ -0,0 +1,24 @@
'use strict';
System.register(['./components/config', './components/metrics', './components/authspage'], function (_export, _context) {
"use strict";
var TimelyAppConfigCtrl, MetricsPageCtrl, AuthsPageCtrl;
return {
setters: [function (_componentsConfig) {
TimelyAppConfigCtrl = _componentsConfig.TimelyAppConfigCtrl;
}, function (_componentsMetrics) {
MetricsPageCtrl = _componentsMetrics.MetricsPageCtrl;
}, function (_componentsAuthspage) {
AuthsPageCtrl = _componentsAuthspage.AuthsPageCtrl;
}],
execute: function () {
_export('ConfigCtrl', TimelyAppConfigCtrl);
_export('MetricsPageCtrl', MetricsPageCtrl);
_export('AuthsPageCtrl', AuthsPageCtrl);
}
};
});
//# sourceMappingURL=module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../src/module.js"],"names":[],"mappings":";;;;;;;;AAAQ,yB,qBAAA,mB;;AACA,qB,sBAAA,e;;AACA,mB,wBAAA,a;;;4BAGN,mB;;iCACA,e;;+BACA,a","file":"module.js","sourcesContent":["import {TimelyAppConfigCtrl} from './components/config';\nimport {MetricsPageCtrl} from './components/metrics';\nimport {AuthsPageCtrl} from './components/authspage';\n\nexport {\n TimelyAppConfigCtrl as ConfigCtrl,\n MetricsPageCtrl,\n AuthsPageCtrl\n};\n"]}
+41
View File
@@ -0,0 +1,41 @@
{
"type": "app",
"name": "Timely",
"id": "timely-app",
"css": {
"dark": "css/dark.css",
"light": "css/light.css"
},
"info": {
"description": "Timely App",
"author": {
"name": "National Security Agency",
"url": "http://github.com/NationalSecurityAgency"
},
"keywords": ["timely app datasource"],
"logos": {
"small": "img/brand_small.png",
"large": "img/brand_large.png"
},
"links": [
{"name": "Project site", "url": "https://github.com/NationalSecurityAgency/timely"},
{"name": "License & Terms", "url": "https://raw.githubusercontent.com/NationalSecurityAgency/timely/master/LICENSE"}
],
"version": "0.0.2",
"updated": "2016-06-30"
},
"includes": [
{"type": "datasource", "name": "Timely"},
{ "type": "page", "name": "Login", "component": "AuthsPageCtrl", "role": "Viewer", "addToNav": true},
{ "type": "page", "name": "Metrics", "component": "MetricsPageCtrl", "role": "Viewer", "addToNav": true}
],
"dependencies": {
"grafanaVersion": "3.x.x",
"plugins": []
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+41
View File
@@ -0,0 +1,41 @@
{
"name": "timely-app",
"private": true,
"version": "0.0.2",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/NationalSecurityAgency/timely.git"
},
"author": "",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/NationalSecurityAgency/timely/issues"
},
"devDependencies": {
"babel": "~6.5.1",
"babel-plugin-transform-es2015-for-of": "^6.8.0",
"grunt": "~0.4.5",
"grunt-babel": "~6.0.0",
"grunt-contrib-clean": "~0.6.0",
"grunt-contrib-copy": "~0.8.2",
"grunt-contrib-uglify": "~0.11.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-execute": "~0.2.2",
"grunt-mocha-test": "~0.12.7",
"grunt-systemjs-builder": "^0.2.5",
"load-grunt-tasks": "~3.2.0"
},
"dependencies": {
"ag-grid": "^5.0.2",
"babel-plugin-transform-es2015-modules-systemjs": "^6.5.0",
"babel-preset-es2015": "^6.5.0",
"dynatable": "alfajango/jquery-dynatable#v0.3.1",
"lodash": "~4.0.0"
},
"homepage": "https://github.com/NationalSecurityAgency/timely"
}
+41
View File
@@ -0,0 +1,41 @@
{
"type": "app",
"name": "Timely",
"id": "timely-app",
"css": {
"dark": "css/dark.css",
"light": "css/light.css"
},
"info": {
"description": "Timely App",
"author": {
"name": "National Security Agency",
"url": "http://github.com/NationalSecurityAgency"
},
"keywords": ["timely app datasource"],
"logos": {
"small": "img/brand_small.png",
"large": "img/brand_large.png"
},
"links": [
{"name": "Project site", "url": "https://github.com/NationalSecurityAgency/timely"},
{"name": "License & Terms", "url": "https://raw.githubusercontent.com/NationalSecurityAgency/timely/master/LICENSE"}
],
"version": "0.0.2",
"updated": "2016-06-30"
},
"includes": [
{"type": "datasource", "name": "Timely"},
{ "type": "page", "name": "Login", "component": "AuthsPageCtrl", "role": "Viewer", "addToNav": true},
{ "type": "page", "name": "Metrics", "component": "MetricsPageCtrl", "role": "Viewer", "addToNav": true}
],
"dependencies": {
"grafanaVersion": "3.x.x",
"plugins": []
}
}
@@ -0,0 +1,10 @@
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-13">OpenTSDB metrics query</span>
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.target' placeholder="events.eventname"></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-13">Show Global Annotations?</span>
<editor-checkbox text="" model="ctrl.annotation.isGlobal"></editor-checkbox>
</div>
</div>
@@ -0,0 +1,52 @@
<div class="page-header">
<h1>Timely Login</h1>
</div>
<div ng-if="ctrl.datasources.length === 0">
<h4>No Timely datasources are defined.</h4>
<i><a href="datasources/new">Create one</a></i>
</div>
<div ng-repeat="source in ctrl.datasources" ng-init="d=source" ng-controller="AuthsCtrl as actrl">
<div class="gf-box">
<div class="gf-box-header p-x-1 p-y-1">
<h3 class="">{{d.name}}
<span class="pull-right">
<h6><i>https://{{d.jsonData.timelyHost}}:{{d.jsonData.httpsPort}}/login</i></h6>
</span>
</h3>
</div>
<div class="gf-box-body">
<div ng-if="d.jsonData.basicAuths" class="p-b-1">
<div class="gf-form">
<span class="gf-form-label width-7">
User
</span>
<input id="timlely-user" class="gf-form-input max-width-21" type="text" ng-model='actrl.basicAuthUser' placeholder="user" required></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">
Password
</span>
<input id="timely-password" class="gf-form-input max-width-21" type="password" ng-model='actrl.basicAuthPassword' placeholder="password" required></input>
</div>
</div>
<div ng-if="actrl.fetching" class="gf-row">
<div class="alert-{{actrl.fetching.status}} alert">
<div class="alert-title">{{actrl.fetching.title}}</div>
</div>
</div>
<div class="gf-form-button-row center-block">
<button type="submit" class="btn btn-success" ng-click="actrl.login(d)">
Login
</button>
</div>
</div>
</div>
</div>
@@ -0,0 +1,97 @@
import _ from 'lodash';
export class AuthsCtrl {
constructor(backendSrv, $rootScope, $timeout){
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.backendSrv = backendSrv;
}
login(datasource){
this.fetching = {};
this.fetching.done = false;
this.fetching.show = true;
this.fetching.status = 'warning';
this.fetching.title = 'Fetching auths...';
//this.$rootScope.appEvent('alert-success', ['Authorization Cookie Received', '']);
//this.$rootScope.appEvent('alert-error', ['Authorization Failed', '']);
//this.$rootScope.appEvent('alert-warning', ['Fetching Authorizations', '']);
var uri = this._getURI(datasource.jsonData);
if(datasource.jsonData.basicAuths){
if( this.basicAuthUser && this.basicAuthPassword){
var authBlob = {username: this.basicAuthUser, password: this.basicAuthPassword};
this.doPostLogin(uri, authBlob);
} else {
this.$rootScope.appEvent('alert-warning', [datasource.name, 'requires User/Password']);
this.fetching.status = 'warning';
this.fetching.title = 'User/Password required';
return;
}
} else {
this.doGetLogin(uri);
}
}
doPostLogin(uri, authBlob){
this.doXHR('POST', uri, authBlob);
}
doGetLogin(uri){
this.doXHR('GET', uri)
}
doXHR(verb, uri, data){
if( !data){
data = "";
}
var r = new XMLHttpRequest();
r.open(verb, uri, true);
r.withCredentials = true;
r.setRequestHeader('Content-Type', 'application/json');
r.onreadystatechange = function () {
this.handleLoginResult(r);
}.bind(this);
r.send(JSON.stringify(data));
}
handleLoginResult(response){
// wait for readyState to be '4 => done'
if (response.readyState != 4) return;
this.fetching.done = true;
switch(response.status){
case 200:
var cookie = (document.cookie.match(/^(?:.*;)?TSESSIONID=([^;]+)(?:.*)?$/)||[,null])[1]
console.log(cookie);
console.log(document.cookie);
this.fetching.status = 'success';
this.fetching.title = 'Login successful';
this.$rootScope.appEvent('alert-success', ['Success', '']);
break;
default:
this.fetching.status = 'warning';
this.fetching.title = response.statusText;
this.$rootScope.appEvent('alert-warning', [response.status+': '+response.statusText,'']);
break;
}
if (r.readyState != 4 || r.status != 200) return;
alert("Success: " + r.getResponseHeader('Set-Cookie'));
}
_getURI(jsonData){
return 'https://'+jsonData.timelyHost+':'+jsonData.httpsPort+'/login'
}
_showResults(){
}
}
@@ -0,0 +1,32 @@
import _ from 'lodash';
import {coreModule, appEvents} from 'app/core/core';
import {AuthsCtrl} from 'plugins/timely-app/components/auths';
export class AuthsPageCtrl {
constructor(backendSrv, datasourceSrv){
this.datasourceSrv = datasourceSrv;
this.datasources = this.getTimelyDatasources();
coreModule.controller('AuthsCtrl', AuthsCtrl);
}
getTimelyDatasources(){
var allSources = this.datasourceSrv.getAll();
var sources = _.map(allSources, function(value, key, col){
var ret = {};
ret.meta = {};
ret.meta.type = col[key].meta.type;
ret.meta.id = col[key].meta.id;
ret.name = key;
ret.jsonData = col[key].jsonData;
return ret;
});
return _.filter(sources, function(d){
return (d.meta.type === 'datasource' && d.meta.id === 'timely-datasource');
});
}
}
AuthsPageCtrl.templateUrl = 'components/auths.html';
@@ -0,0 +1,10 @@
<h3 class="page-heading">Timely App Configuration</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label">Currently, all configuration is done in the Timely datasource.</span>
</div>
</div>
</div>
@@ -0,0 +1,9 @@
export class TimelyAppConfigCtrl {
constructor() {
}
}
TimelyAppConfigCtrl.templateUrl = 'components/config.html';
@@ -0,0 +1,53 @@
<div class="page-header">
<h1>Metrics Browser</h1>
</div>
<div ng-hide="ctrl.hasDatasource" >
<h4>No Timely datasources are defined.</h4>
<i><a href="datasources/new">Create one</a></i>
</div>
<div class="gf-box" ng-show="ctrl.hasDatasource">
<div class="gf-box-header">
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-label width-7 p-y-1 p-x-1">Datasource</span>
<div class="gf-form-select-wrapper gf-form-select-wrapper--has-help-icon max-width-24">
<select class="gf-form-input"
ng-model="ctrl.source"
ng-options="d.name for d in ctrl.datasources"
ng-change=ctrl.updateDatasource()></select>
<info-popover mode="right-absolute">
Select a Datasource<br>
</info-popover>
</div>
</div>
</div>
</div>
<div class="gf-box-body">
<div class="grafana-options-table">
<div class="gf-form width-20 pull-right">
<input type="text" class="gf-form-input" placeholder="filter" ng-model="searchText">
</input>
</div>
<table>
<tr>
<th>Metric</th>
<th>Tags</th>
</tr>
<tr ng-repeat="metric in ctrl.metrics | filter:searchText">
<td>{{metric.metric}}</td>
<td class="p-x-1" ng-repeat="tag in metric.tags">
{{tag.name}} = {{tag.values}}
</td>
</tr>
</table>
</div>
</div>
</div>
@@ -0,0 +1,76 @@
import _ from 'lodash';
import {AuthsPageCtrl} from 'plugins/timely-app/components/authspage';
export class MetricsPageCtrl {
constructor(backendSrv, datasourceSrv){
this.hasDatasource = false;
this.backendSrv = backendSrv;
var apc = new AuthsPageCtrl(backendSrv, datasourceSrv);
this.datasources = apc.getTimelyDatasources();
if( this.datasources.length > 0){
this.source = this.datasources[0];
this.updateDatasource();
}
}
updateDatasource(){
this._clearMetrics();
var uri = this._getURI(this.source.jsonData);
this.backendSrv.request(
{ method: 'GET',
url: uri,
withCredentials: true,
params: "" }
).then(
response => {
this.metrics = this._transformMetrics(response.metrics);
this.hasDatasource = true;
});
}
_clearMetrics(){
this.metrics = [];
}
_transformMetrics(metrics){
var ret = [];
_.each(metrics, function(value, idx){
var metric = {};
metric.metric = value.metric;
metric.tags = [];
_.map(value.tags, function(tag, key){
// result || (result = {});
// result['name'] = tag.key;
// result['values'] || (result['values'] = []);
// result['values'].push(tag.value);
var gIdx = _.findIndex(metric.tags, {'name': tag.key});
var grouped;
if( gIdx == -1 ){
grouped = {name: '', values: []};
} else {
grouped = metric.tags[gIdx];
}
grouped.values.push(tag.value);
grouped.values = _.sortBy(_.uniq(grouped.values))
grouped.name = tag.key;
if( gIdx > -1 ){
metric.tags[gIdx] = grouped;
} else {
metric.tags.push(grouped);
}
}.bind(metric));
metric.tags = _.sortBy(metric.tags, 'name')
ret.push(metric);
}.bind(ret));
return _.sortBy(ret, 'metric');
}
_getURI(jsonData){
return 'https://'+jsonData.timelyHost+':'+jsonData.httpsPort+'/api/metrics'
}
}
MetricsPageCtrl.templateUrl = 'components/metrics.html';
@@ -0,0 +1,257 @@
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline">
<div class="gf-form max-width-25">
<label class="gf-form-label query-keyword width-8">
Metric
<label class="gf-form-label" bs-tooltip="ctrl.errors.metric" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.metric">
<i class="fa fa-warning"></i>
</label>
</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.metric"
spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()">
</input>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">
Aggregator
<a bs-tooltip="ctrl.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.aggregator">
<i class="fa fa-warning"></i>
</a>
</label>
<div class="gf-form-select-wrapper max-width-15">
<select ng-model="ctrl.target.aggregator" class="gf-form-input"
ng-options="agg for agg in ctrl.aggregators"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-6">
Alias:
<info-popover mode="right-normal">
Use patterns like $tag_tagname to replace part of the alias for a tag value
</info-popover>
</label>
<input type="text" class="gf-form-input"
ng-model="ctrl.target.alias"
spellcheck='false'
placeholder="series alias"
data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()"></input>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-25">
<label class="gf-form-label query-keyword width-8">Down sample</label>
<input type="text" class="gf-form-input"
ng-model="ctrl.target.downsampleInterval"
ng-model-onblur
ng-change="ctrl.targetBlur()"
placeholder="interval"></input>
<info-popover mode="right-absolute">
blank for auto, or for example <code>1m</code>
</info-popover>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">Aggregator</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.downsampleAggregator" class="gf-form-input"
ng-options="agg for agg in ctrl.aggregators"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<div class="gf-form" ng-if="ctrl.tsdbVersion >= 2">
<label class="gf-form-label query-keyword width-6">Fill</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.downsampleFillPolicy" class="gf-form-input"
ng-options="agg for agg in ctrl.fillPolicies"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<gf-form-switch class="gf-form"
label="Disable downsampling"
label-class="query-keyword"
checked="ctrl.target.disableDownsampling"
on-change="ctrl.targetBlur()">
</gf-form-switch>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.tsdbVersion >= 2">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8">
Filters
<info-popover mode="right-normal">
Filters does not work with tags, either of the two will work but not both.
</info-popover>
</label>
<div ng-repeat="fil in ctrl.target.filters track by $index" class="gf-form-label">
{{fil.tagk}}&nbsp;=&nbsp;{{fil.type}}&#40;{{fil.filter}}&#41;&nbsp;&#44&nbsp;groupBy&nbsp;=&nbsp;{{fil.groupBy}}
<a ng-click="ctrl.editFilter(fil, $index)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeFilter($index)">
<i class="fa fa-remove"></i>
</a>
</div>
<label class="gf-form-label query-keyword" ng-hide="ctrl.addFilterMode">
<a ng-click="ctrl.addFilter()">
<i class="fa fa-plus"></i>
</a>
</label>
</div>
<div class="gf-form-inline" ng-show="ctrl.addFilterMode">
<div class="gf-form">
<input type="text" class="gf-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentFilterKey" placeholder="key">
</input>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">Type</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.currentFilterType" class="gf-form-input" ng-options="filType for filType in ctrl.filterTypes">
</select>
</div>
</div>
<div class="gf-form">
<input type="text" class="gf-form-input" spellcheck='false' bs-typeahead="ctrl.suggestTagValues" data-min-length=0 data-items=100 ng-model="ctrl.target.currentFilterValue" placeholder="filter">
</input>
</div>
<gf-form-switch class="gf-form"
label="Group by"
label-class="query-keyword"
checked="ctrl.target.currentFilterGroupBy"
on-change="ctrl.targetBlur()">
</gf-form-switch>
<div class="gf-form" ng-show="ctrl.addFilterMode">
<label class="gf-form-label" ng-show="ctrl.errors.filters">
<a bs-tooltip="ctrl.errors.filters" style="color: rgb(229, 189, 28)" >
<i class="fa fa-warning"></i>
</a>
</label>
<label class="gf-form-label">
<a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">add filter</a>
<a ng-click="ctrl.closeAddFilterMode()">
<i class="fa fa-remove"></i>
</a>
</label>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8">
Tags
<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion >= 2">
Please use filters, tags are deprecated in opentsdb 2.2
</info-popover>
</label>
</div>
<div class="gf-form" ng-repeat="(key, value) in ctrl.target.tags track by $index" class="gf-form">
<label class="gf-form-label">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="ctrl.editTag(key, value)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeTag(key)">
<i class="fa fa-remove"></i>
</a>
</label>
</div>
<div class="gf-form" ng-hide="ctrl.addTagMode">
<label class="gf-form-label query-keyword">
<a ng-click="ctrl.addTag()"><i class="fa fa-plus"></i></a>
</label>
</div>
<div class="gf-form" ng-show="ctrl.addTagMode">
<input type="text"
class="gf-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentTagKey" placeholder="key">
</input>
<input type="text" class="gf-form-input"
spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value">
</input>
<label class="gf-form-label" ng-show="ctrl.errors.tags">
<a bs-tooltip="ctrl.errors.tags" style="color: rgb(229, 189, 28)" >
<i class="fa fa-warning"></i>
</a>
</label>
<label class="gf-form-label" >
<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags">add tag</a>
<a ng-click="ctrl.closeAddTagMode()"><i class="fa fa-remove"></i></a>
</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<gf-form-switch class="gf-form" label="Rate" label-class="width-8 query-keyword" checked="ctrl.target.shouldComputeRate" on-change="ctrl.targetBlur()">
</gf-form-switch>
<gf-form-switch ng-hide="!ctrl.target.shouldComputeRate"
class="gf-form" label="Counter" label-class="query-keyword" checked="ctrl.target.isCounter" on-change="ctrl.targetBlur()">
</gf-form-switch>
<div class="gf-form" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
<label class="gf-form-label query-keyword">Counter Max</label>
<input type="text" class="gf-form-input"
ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterMax" spellcheck='false'
placeholder="max value" ng-model-onblur
ng-blur="ctrl.targetBlur()">
</input>
<label class="gf-form-label query-keyword">Reset Value</label>
<input type="text" class="tight-form-input input-small"
ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterResetValue" spellcheck='false'
placeholder="reset value" ng-model-onblur
ng-blur="ctrl.targetBlur()">
</input>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>
View File
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,3 @@
.timely-app-heading {
text-align: center;
}
@@ -0,0 +1,14 @@
import _ from 'lodash';
export class DatasourceConfigCtrl {
constructor() {
this.current.jsonData = this.current.jsonData || {};
this.current.access = 'direct';
this.current.basicAuth = false;
this.current.withCredentials = true;
this.current.suggestHost = 'localhost';
}
}
DatasourceConfigCtrl.templateUrl = 'datasource/partials/config.html';
@@ -0,0 +1,434 @@
{
"__inputs": [
{
"name": "DS_NAME",
"type": "datasource",
"pluginId": "timely-datasource"
}
],
"id": null,
"title": "${DS_NAME} - Status",
"originalTitle": "${DS_NAME} - Status",
"tags": ["imported"],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)",
"thresholdLine": false
},
"id": 1,
"legend": {
"alignAsTable": true,
"avg": false,
"current": true,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": true,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"isCounter": false,
"metric": "timely.metrics.received",
"refId": "A",
"shouldComputeRate": false
},
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"metric": "timely.keys.meta.inserted",
"refId": "B"
},
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"metric": "timely.keys.metric.inserted",
"refId": "C"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Timely Status",
"tooltip": {
"msResolution": true,
"shared": false,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": "Keys",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"title": "Row"
},
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 2,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "$tag_host",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {
"host": ".*"
}
}
],
"timeFrom": null,
"timeShift": null,
"title": "CPU User by Host",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 3,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "$tag_rack",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {
"rack": ".*"
}
}
],
"timeFrom": null,
"timeShift": null,
"title": "CPU User by Rack",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"datasource": "${DS_NAME}",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 4,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {}
}
],
"timeFrom": null,
"timeShift": null,
"title": "Cumulative CPU",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
}
],
"title": "New row"
}
],
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"refresh": "5s",
"schemaVersion": 12,
"version": 1,
"links": []
}
@@ -0,0 +1,461 @@
import _ from "lodash";
import angular from 'angular';
import * as dateMath from '../../../app/core/utils/datemath';
export class TimelyDatasource {
// arguments injected by angular
constructor(instanceSettings, $q, backendSrv, templateSrv, contextSrv) {
this.type = instanceSettings.type;
this.name = instanceSettings.name;
this.jsonData = instanceSettings.jsonData || {};
this.httpsPort = instanceSettings.jsonData.httpsPort;
this.timelyHost = instanceSettings.jsonData.timelyHost;
this.wsPort = instanceSettings.jsonData.wsPort;
if( !instanceSettings.jsonData.basicAuths){
instanceSettings.jsonData.basicAuths = false;
}
this.url = this._makeHttpsUrl()
this.withCredentials = true;
this.basicAuth = false;
this.supportMetrics = true;
this.tagKeys = {};
this.$q = $q;
this.backendSrv = backendSrv;
this.templateSrv = templateSrv;
this.contextSrv = contextSrv;
this.aggregatorsPromise = null;
this.filterTypesPromise = null;
}
// REQUIRED INTERFACE
query(options){
var start = this.convertToTSDBTime(options.rangeRaw.from, false);
var end = this.convertToTSDBTime(options.rangeRaw.to, true);
var qs = [];
_.each(options.targets, function(target) {
if (!target.metric) { return; }
qs.push(this.convertTargetToQuery(target, options));
}.bind(this) );
var queries = _.compact(qs);
// No valid targets, return the empty result to save a round trip.
if (_.isEmpty(queries)) {
var d = this.$q.defer();
d.resolve({ data: [] });
return d.promise;
}
var groupByTags = {};
_.each(queries, function(query) {
if (query.filters && query.filters.length > 0) {
_.each(query.filters, function(val) {
groupByTags[val.tagk] = true;
});
} else {
_.each(query.tags, function(val, key) {
groupByTags[key] = true;
});
}
});
return this.performTimeSeriesQuery(queries, start, end).then(function(response) {
var metricToTargetMapping = this.mapMetricsToTargets(response.data, options);
var result = _.map(response.data, function(metricData, index) {
index = metricToTargetMapping[index];
if (index === -1) {
index = 0;
}
this._saveTagKeys(metricData);
return this.transformMetricData(metricData, groupByTags, options.targets[index], options);
}.bind(this));
return { data: result };
}.bind(this));
}
// REQUIRED INTERFACE
testDatasource(){
return this.backendSrv.datasourceRequest({
url: this.url + '/version',
method: 'GET'
}).then(response => {
if (response.status === 200) {
return { status: "success", message: "Data source is working", title: "Success" };
}
});
}
// REQUIRED INTERFACE
annotationQuery(options){
var start = convertToTSDBTime(options.rangeRaw.from, false);
var end = convertToTSDBTime(options.rangeRaw.to, true);
var qs = [];
var eventList = [];
qs.push({ aggregator:"sum", metric:options.annotation.target });
var queries = _.compact(qs);
return this.performTimeSeriesQuery(queries, start, end).then(function(results) {
if(results.data[0]) {
var annotationObject = results.data[0].annotations;
if(options.annotation.isGlobal){
annotationObject = results.data[0].globalAnnotations;
}
if(annotationObject) {
_.each(annotationObject, function(annotation) {
var event = {
title: annotation.description,
time: Math.floor(annotation.startTime) * 1000,
text: annotation.notes,
annotation: options.annotation
};
eventList.push(event);
});
}
}
return eventList;
}.bind(this));
}
// REQUIRED INTERFACE
metricFindQuery(query) {
if (!query) { return this.$q.when([]); }
var interpolated;
try {
interpolated = this.templateSrv.replace(query);
}
catch (err) {
return this.$q.reject(err);
}
var responseTransform = function(result) {
return _.map(result, function(value) {
return {text: value};
});
};
var metrics_regex = /metrics\((.*)\)/;
var tag_names_regex = /tag_names\((.*)\)/;
var tag_values_regex = /tag_values\((.*?),\s?(.*)\)/;
var tag_names_suggest_regex = /suggest_tagk\((.*)\)/;
var tag_values_suggest_regex = /suggest_tagv\((.*)\)/;
var metrics_query = interpolated.match(metrics_regex);
if (metrics_query) {
return this._performSuggestQuery(metrics_query[1], 'metrics').then(responseTransform);
}
var tag_names_query = interpolated.match(tag_names_regex);
if (tag_names_query) {
return this._performMetricKeyLookup(tag_names_query[1]).then(responseTransform);
}
var tag_values_query = interpolated.match(tag_values_regex);
if (tag_values_query) {
return this._performMetricKeyValueLookup(tag_values_query[1], tag_values_query[2]).then(responseTransform);
}
var tag_names_suggest_query = interpolated.match(tag_names_suggest_regex);
if (tag_names_suggest_query) {
return this._performSuggestQuery(tag_names_suggest_query[1], 'tagk').then(responseTransform);
}
var tag_values_suggest_query = interpolated.match(tag_values_suggest_regex);
if (tag_values_suggest_query) {
return this._performSuggestQuery(tag_values_suggest_query[1], 'tagv').then(responseTransform);
}
return this.$q.when([]);
}
performTimeSeriesQuery(queries, start, end){
var msResolution = true;
var reqBody = {
start: start,
queries: queries,
msResolution: msResolution,
globalAnnotations: true,
showQuery: true
};
// Relative queries (e.g. last hour) don't include an end time
if (end) {
reqBody.end = end;
}
var options = {
method: 'POST',
url: this.url + '/api/query',
data: reqBody
};
this._addCredentialOptions(options);
// In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests
// go as POST rather than OPTIONS+POST
options.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
return this.backendSrv.datasourceRequest(options);
}
_saveTagKeys(metricData) {
var tagKeys = Object.keys(metricData.tags);
_.each(metricData.aggregateTags, function(tag) {
tagKeys.push(tag);
});
this.tagKeys[metricData.metric] = tagKeys;
}
suggestTagKeys(metric) {
return this.$q.when(this.tagKeys[metric] || []);
}
_performSuggestQuery(query, type) {
return this._get('/api/suggest', {type: type, q: query, max: 1000}).then(function(result) {
return result.data;
});
}
_performMetricKeyValueLookup(metric, keys) {
if(!metric || !keys) {
return $q.when([]);
}
var keysArray = keys.split(",").map(function(key) {
return key.trim();
});
var key = keysArray[0];
var keysQuery = key + "=*";
if (keysArray.length > 1) {
keysQuery += "," + keysArray.splice(1).join(",");
}
var m = metric + "{" + keysQuery + "}";
return this._get('/api/search/lookup', {m: m, limit: 3000}).then(function(result) {
result = result.data.results;
var tagvs = [];
_.each(result, function(r) {
if (tagvs.indexOf(r.tags[key]) === -1) {
tagvs.push(r.tags[key]);
}
});
return tagvs;
});
}
_performMetricKeyLookup(metric) {
if(!metric) { return this.$q.when([]); }
return this._get('/api/search/lookup', {m: metric, limit: 1000}).then(function(result) {
result = result.data.results;
var tagks = [];
_.each(result, function(r) {
_.each(r.tags, function(tagv, tagk) {
if(tagks.indexOf(tagk) === -1) {
tagks.push(tagk);
}
});
});
return tagks;
});
}
_get(relativeUrl, params) {
var options = {
method: 'GET',
url: this.url + relativeUrl,
params: params,
};
this._addCredentialOptions(options);
return this.backendSrv.datasourceRequest(options);
}
_addCredentialOptions(options) {
if (this.basicAuth || this.withCredentials) {
options.withCredentials = true;
}
if (this.basicAuth) {
options.headers = {"Authorization": this.basicAuth};
}
}
getAggregators(){
if (this.aggregatorsPromise) { return this.aggregatorsPromise; }
this.aggregatorsPromise = this._get('/api/aggregators').then(function(result) {
if (result.data && _.isArray(result.data)) {
return result.data.sort();
}
return [];
});
return this.aggregatorsPromise;
}
getFilterTypes() {
if (this.filterTypesPromise) { return this.filterTypesPromise; }
// filters are for a newer opentsdb api version than Timely originally copied.
this.filterTypesPromise = this._get('/api/aggregators').then(function(result) {
return [];
});
return this.filterTypesPromise;
}
transformMetricData(md, groupByTags, target, options) {
var metricLabel = this.createMetricLabel(md, target, groupByTags, options);
var dps = [];
// TSDB returns datapoints has a hash of ts => value.
// Can't use _.pairs(invert()) because it stringifies keys/values
_.each(md.dps, function (v, k) {
dps.push([v, k * 1]);
});
return { target: metricLabel, datapoints: dps };
}
createMetricLabel(md, target, groupByTags, options) {
if (target.alias) {
var scopedVars = _.clone(options.scopedVars || {});
_.each(md.tags, function(value, key) {
scopedVars['tag_' + key] = {value: value};
});
return this.templateSrv.replace(target.alias, scopedVars);
}
var label = md.metric;
var tagData = [];
if (!_.isEmpty(md.tags)) {
_.each(_.pairs(md.tags), function(tag) {
if (_.has(groupByTags, tag[0])) {
tagData.push(tag[0] + "=" + tag[1]);
}
});
}
if (!_.isEmpty(tagData)) {
label += "{" + tagData.join(", ") + "}";
}
return label;
}
convertTargetToQuery(target, options) {
if (!target.metric || target.hide) {
return null;
}
var query = {
metric: this.templateSrv.replace(target.metric, options.scopedVars),
aggregator: "avg"
};
if (target.aggregator) {
query.aggregator = this.templateSrv.replace(target.aggregator);
}
if (target.shouldComputeRate) {
query.rate = true;
query.rateOptions = {
counter: !!target.isCounter
};
if (target.counterMax && target.counterMax.length) {
query.rateOptions.counterMax = parseInt(target.counterMax);
}
if (target.counterResetValue && target.counterResetValue.length) {
query.rateOptions.resetValue = parseInt(target.counterResetValue);
}
}
if (!target.disableDownsampling) {
var interval = this.templateSrv.replace(target.downsampleInterval || options.interval);
if (interval.match(/\.[0-9]+s/)) {
interval = parseFloat(interval)*1000 + "ms";
}
query.downsample = interval + "-" + target.downsampleAggregator;
if (target.downsampleFillPolicy && target.downsampleFillPolicy !== "none") {
query.downsample += "-" + target.downsampleFillPolicy;
}
}
if (target.filters && target.filters.length > 0) {
query.filters = angular.copy(target.filters);
if(query.filters){
for(var filter_key in query.filters){
query.filters[filter_key].filter = this.templateSrv.replace(query.filters[filter_key].filter, options.scopedVars, 'pipe');
}
}
} else {
query.tags = angular.copy(target.tags);
if(query.tags){
for(var tag_key in query.tags){
query.tags[tag_key] = this.templateSrv.replace(query.tags[tag_key], options.scopedVars, 'pipe');
}
}
}
return query;
}
mapMetricsToTargets(metrics, options) {
var interpolatedTagValue;
return _.map(metrics, function(metricData) {
return _.findIndex(options.targets, function(target) {
if (target.filters && target.filters.length > 0) {
return target.metric === metricData.metric;
} else {
return target.metric === metricData.metric &&
_.all(target.tags,
function(tagV, tagK) {
interpolatedTagValue = this.templateSrv.replace(tagV, options.scopedVars, 'pipe');
return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*";
}.bind(this));
}
}.bind(this));
}.bind(this));
}
convertToTSDBTime(date, roundUp) {
if (date === 'now') {
return null;
}
date = dateMath.parse(date, roundUp);
return date.valueOf();
}
_makeHttpsUrl(){
return 'https://' + this.timelyHost + ':' + this.httpsPort
}
_makeWsUrl(){
return 'ws://' + this.timelyHost + ':' + this.wsPort
}
}
@@ -0,0 +1,13 @@
import {TimelyDatasource} from './datasource';
import {TimelyQueryCtrl} from './query_ctrl';
import {DatasourceConfigCtrl} from './config';
class TimelyAnnotationsQueryCtrl {}
TimelyAnnotationsQueryCtrl.templateUrl = 'components/annotations.editor.html';
export {
TimelyDatasource as Datasource,
TimelyQueryCtrl as QueryCtrl,
DatasourceConfigCtrl as ConfigCtrl,
TimelyAnnotationsQueryCtrl as AnnotationsQueryCtrl
};
@@ -0,0 +1,66 @@
<div class="gf-form-group">
<div class="gf-form-group">
<h3 class="page-heading">Server Settings</h3>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-7">Hostname</span>
<input class="gf-form-input" type="text"
ng-model="ctrl.current.jsonData.timelyHost" placeholder="{{ctrl.current.jsonData.suggestHost}}"
min-length="0"
ng-pattern="/(\w+:{0,1}\w*@)?(\S+)?$/" required></input>
<info-popover mode="right-absolute">
<p>Hostname of timely server</p>
</info-popover>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-7">HTTPS Port</span>
<input class="gf-form-input" type="text"
ng-model="ctrl.current.jsonData.httpsPort"
min-length="0"
ng-pattern="/[0-9]+/" required></input>
<info-popover mode="right-absolute">
<p>Port number for Timely HTTPS access</p>
</info-popover>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-7">WS Port</span>
<input class="gf-form-input" type="text"
ng-model="ctrl.current.jsonData.wsPort"
min-length="0"
ng-pattern="/[0-9]+/"></input>
<info-popover mode="right-absolute">
<p>Port number for Timely WebSocket access</p>
</info-popover>
</div>
</div>
<gf-form-switch class="gf-form "
label="Basic Auths"
checked="ctrl.current.jsonData.basicAuths" switch-class="max-width-6">
</gf-form-switch>
</div>
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label">
<a ng-href="https://{{ctrl.current.jsonData.timelyHost}}:{{ctrl.current.jsonData.httpsPort}}/version" target="_blank">
Browser Cert Check
</a>
<info-popover mode="right">
<p>Opens a new browser to test https access, allows you to accept a cert, etc.</p>
</info-popover>
</span>
</div>
</div>
</div>
@@ -0,0 +1,24 @@
{
"type": "datasource",
"name": "Timely",
"id": "timely-datasource",
"metrics": true,
"defaultMatchFormat": "pipe",
"annotations": true,
"includes": [
{"type": "dashboard", "name": "Timely Metrics", "path": "dashboards/standalone_test.json"}
],
"info": {
"author": {
"name": "National Security Agency",
"url": "http://github.com/NationalSecurityAgency/timely"
},
"logos": {
"small": "../timely-app/img/brand_small.png",
"large": "../timely-app/img/brand_large.png"
}
}
}
@@ -0,0 +1,195 @@
import _ from 'lodash';
import {QueryCtrl} from 'app/plugins/sdk';
export class TimelyQueryCtrl extends QueryCtrl {
constructor($scope, $injector, uiSegmentSrv) {
super($scope, $injector);
this.errors = this.validateTarget();
this.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax'];
this.fillPolicies = ['none', 'nan', 'null', 'zero'];
this.filterTypes = ['wildcard','iliteral_or','not_iliteral_or','not_literal_or','iwildcard','literal_or','regexp'];
if (!this.target.aggregator) {
this.target.aggregator = 'sum';
}
if (!this.target.downsampleAggregator) {
this.target.downsampleAggregator = 'avg';
}
if (!this.target.downsampleFillPolicy) {
this.target.downsampleFillPolicy = 'none';
}
this.datasource.getAggregators().then((aggs) => {
if (aggs.length !== 0) {
this.aggregators = aggs;
}
});
this.datasource.getFilterTypes().then((filterTypes) => {
if (filterTypes.length !== 0) {
this.filterTypes = filterTypes;
}
});
// needs to be defined here as it is called from typeahead
this.suggestMetrics = (query, callback) => {
this.datasource.metricFindQuery('metrics(' + query + ')')
.then(this.getTextValues)
.then(callback);
};
this.suggestTagKeys = (query, callback) => {
this.datasource.suggestTagKeys(this.target.metric).then(callback);
};
this.suggestTagValues = (query, callback) => {
this.datasource.metricFindQuery('suggest_tagv(' + query + ')')
.then(this.getTextValues)
.then(callback);
};
}
targetBlur() {
this.errors = this.validateTarget();
this.refresh();
}
getTextValues(metricFindResult) {
return _.map(metricFindResult, function(value) { return value.text; });
}
addTag() {
if (this.target.filters && this.target.filters.length > 0) {
this.errors.tags = "Please remove filters to use tags, tags and filters are mutually exclusive.";
}
if (!this.addTagMode) {
this.addTagMode = true;
return;
}
if (!this.target.tags) {
this.target.tags = {};
}
this.errors = this.validateTarget();
if (!this.errors.tags) {
this.target.tags[this.target.currentTagKey] = this.target.currentTagValue;
this.target.currentTagKey = '';
this.target.currentTagValue = '';
this.targetBlur();
}
this.addTagMode = false;
}
removeTag(key) {
delete this.target.tags[key];
this.targetBlur();
}
editTag(key, value) {
this.removeTag(key);
this.target.currentTagKey = key;
this.target.currentTagValue = value;
this.addTag();
}
closeAddTagMode() {
this.addTagMode = false;
return;
}
addFilter() {
if (this.target.tags && _.size(this.target.tags) > 0) {
this.errors.filters = "Please remove tags to use filters, tags and filters are mutually exclusive.";
}
if (!this.addFilterMode) {
this.addFilterMode = true;
return;
}
if (!this.target.filters) {
this.target.filters = [];
}
if (!this.target.currentFilterType) {
this.target.currentFilterType = 'iliteral_or';
}
if (!this.target.currentFilterGroupBy) {
this.target.currentFilterGroupBy = false;
}
this.errors = this.validateTarget();
if (!this.errors.filters) {
var currentFilter = {
type: this.target.currentFilterType,
tagk: this.target.currentFilterKey,
filter: this.target.currentFilterValue,
groupBy: this.target.currentFilterGroupBy
};
this.target.filters.push(currentFilter);
this.target.currentFilterType = 'literal_or';
this.target.currentFilterKey = '';
this.target.currentFilterValue = '';
this.target.currentFilterGroupBy = false;
this.targetBlur();
}
this.addFilterMode = false;
}
removeFilter(index) {
this.target.filters.splice(index, 1);
this.targetBlur();
}
editFilter(fil, index) {
this.removeFilter(index);
this.target.currentFilterKey = fil.tagk;
this.target.currentFilterValue = fil.filter;
this.target.currentFilterType = fil.type;
this.target.currentFilterGroupBy = fil.groupBy;
this.addFilter();
}
closeAddFilterMode() {
this.addFilterMode = false;
return;
}
validateTarget() {
var errs = {};
if (this.target.shouldDownsample) {
try {
if (this.target.downsampleInterval) {
kbn.describe_interval(this.target.downsampleInterval);
} else {
errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h').";
}
} catch (err) {
errs.downsampleInterval = err.message;
}
}
if (this.target.tags && _.has(this.target.tags, this.target.currentTagKey)) {
errs.tags = "Duplicate tag key '" + this.target.currentTagKey + "'.";
}
return errs;
}
}
TimelyQueryCtrl.templateUrl = 'components/query.editor.html';
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+9
View File
@@ -0,0 +1,9 @@
import {TimelyAppConfigCtrl} from './components/config';
import {MetricsPageCtrl} from './components/metrics';
import {AuthsPageCtrl} from './components/authspage';
export {
TimelyAppConfigCtrl as ConfigCtrl,
MetricsPageCtrl,
AuthsPageCtrl
};
+9 -2
View File
@@ -3,13 +3,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.1</version>
<version>0.0.2</version>
<packaging>pom</packaging>
<modules>
<module>server</module>
<module>collectd-timely-plugin</module>
<module>collectd-nsq-plugin</module>
<module>collectd-plugin-parent</module>
<module>grafana</module>
</modules>
<properties>
<!-- Configure the os-maven-plugin extension to expand the classifier on -->
@@ -29,7 +30,8 @@
<version.javassist>3.20.0-GA</version.javassist>
<version.junit>4.11</version.junit>
<version.log4j>2.5</version.log4j>
<version.netty>4.0.36.Final</version.netty>
<version.netty>4.0.38.Final</version.netty>
<version.netty-tcnative>1.1.33.Fork18</version.netty-tcnative>
<version.security>4.0.4.RELEASE</version.security>
<version.slf4j>1.7.12</version.slf4j>
</properties>
@@ -217,6 +219,11 @@
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
+17
View File
@@ -0,0 +1,17 @@
FROM centos:centos7
MAINTAINER Jim Klucar "[email protected]"
VOLUME /timely-server-src
RUN yum -y update && \
yum -y install java-1.8.0-openjdk-devel maven openssl apr && \
mkdir -p /timely/bin
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.91-1.b14.el7_2.x86_64
EXPOSE 9804 54321 54322
ENV TIMELY_VERSION 0.0.2-SNAPSHOT
#CMD /timely/bin/timely-standalone.sh
+20
View File
@@ -0,0 +1,20 @@
# Docker Example
Build the server for use in the centos7 Docker container
```
mvn clean package -Dos.detected.classifier=linux-x86_64-fedora
```
Launch the various containers using docker-compose
```
docker-compose up
```
This starts timely, grafana and a process to insert fake test data. It also mounts the timely grafana plugin for grafana.
Browse to the UIs:
[Grafana](https://localhost:3000) User: admin Password: admin
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
set -e
/timely-server-src/bin/docker/deploy-timely-build.sh
/timely/bin/timely-standalone.sh
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
set -e
tar xzf /timely-server-src/target/timely-server-$TIMELY_VERSION-dist.tar.gz -C /timely --strip-components 1
+19
View File
@@ -0,0 +1,19 @@
#!/bin/sh
/timely-server-src/bin/docker/deploy-timely-build.sh
/timely-server-src/bin/docker/wait-for-it.sh timely:54322 -t 45
# create datasource in grafana
curl -X POST -u admin:admin -H "Content-Type: application/json" -d \
'{"name":"Timely","type":"opentsdb","url":"https://timely:54322","access":"proxy","jsonData":{"tsdbVersion":1,"tsdbResolution":1}}' \
http://grafana:3000/api/datasources
# insert the dashboard
curl -X POST -u admin:admin -H "Content-Type: application/json" \
-d @/timely-server-src/bin/docker/standalone_test.json \
http://grafana:3000/api/dashboards/db
/timely/bin/insert-test-data.sh
+2
View File
@@ -0,0 +1,2 @@
pki/
+313
View File
@@ -0,0 +1,313 @@
##################### Grafana Configuration Example #####################
#
# Everything has defaults so you only need to uncomment things you want to
# change
# possible values : production, development
; app_mode = production
# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
; instance_name = ${HOSTNAME}
#################################### Paths ####################################
[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
#
;data = /var/lib/grafana
#
# Directory where grafana can store logs
#
;logs = /var/log/grafana
#
# Directory where grafana will automatically scan and look for plugins
#
;plugins = /var/lib/grafana/plugins
#
#################################### Server ####################################
[server]
# Protocol (http or https)
protocol = https
# The ip address to bind to, empty will bind to all interfaces
;http_addr =
# The http port to use
http_port = 3000
# The public facing domain name used to access grafana from a browser
;domain = localhost
# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
;enforce_domain = false
# The full public facing url
;root_url = %(protocol)s://%(domain)s:%(http_port)s/
# Log web requests
;router_logging = false
# the path relative working path
;static_root_path = public
# enable gzip
;enable_gzip = false
# https certs & key file
cert_file = /etc/grafana/pki/timely-cert.pem
cert_key = /etc/grafana/pki/timely-key.pem
#################################### Database ####################################
[database]
# Either "mysql", "postgres" or "sqlite3", it's your choice
;type = sqlite3
;host = 127.0.0.1:3306
;name = grafana
;user = root
;password =
# For "postgres" only, either "disable", "require" or "verify-full"
;ssl_mode = disable
# For "sqlite3" only, path relative to data_path setting
;path = grafana.db
#################################### Session ####################################
[session]
# Either "memory", "file", "redis", "mysql", "postgres", default is "file"
;provider = file
# Provider config options
# memory: not have any config yet
# file: session dir path, is relative to grafana data_path
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana`
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
# postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
;provider_config = sessions
# Session cookie name
;cookie_name = grafana_sess
# If you use session in https only, default is false
;cookie_secure = false
# Session life time, default is 86400
;session_life_time = 86400
#################################### Analytics ####################################
[analytics]
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
# No ip addresses are being tracked, only simple counters to track
# running instances, dashboard and error counts. It is very helpful to us.
# Change this option to false to disable reporting.
;reporting_enabled = true
# Set to false to disable all checks to https://grafana.net
# for new vesions (grafana itself and plugins), check is used
# in some UI views to notify that grafana or plugin update exists
# This option does not cause any auto updates, nor send any information
# only a GET request to http://grafana.net to get latest versions
check_for_updates = true
# Google Analytics universal tracking code, only enabled if you specify an id here
;google_analytics_ua_id =
#################################### Security ####################################
[security]
# default admin user, created on startup
;admin_user = admin
# default admin password, can be changed before first start of grafana, or in profile settings
;admin_password = admin
# used for signing
;secret_key = SW2YcwTIb9zpOOhoPsMm
# Auto-login remember days
;login_remember_days = 7
;cookie_username = grafana_user
;cookie_remember_name = grafana_remember
# disable gravatar profile images
;disable_gravatar = false
# data source proxy whitelist (ip_or_domain:port separated by spaces)
;data_source_proxy_whitelist =
[snapshots]
# snapshot sharing options
;external_enabled = true
;external_snapshot_url = https://snapshots-origin.raintank.io
;external_snapshot_name = Publish to snapshot.raintank.io
#################################### Users ####################################
[users]
# disable user signup / registration
;allow_sign_up = true
# Allow non admin users to create organizations
;allow_org_create = true
# Set to true to automatically assign new users to the default organization (id 1)
;auto_assign_org = true
# Default role new users will be automatically assigned (if disabled above is set to true)
;auto_assign_org_role = Viewer
# Background text for the user field on the login page
;login_hint = email or username
# Default UI theme ("dark" or "light")
;default_theme = dark
#################################### Anonymous Auth ##########################
[auth.anonymous]
# enable anonymous access
;enabled = false
# specify organization name that should be used for unauthenticated users
;org_name = Main Org.
# specify role for unauthenticated users
;org_role = Viewer
#################################### Github Auth ##########################
[auth.github]
;enabled = false
;allow_sign_up = false
;client_id = some_id
;client_secret = some_secret
;scopes = user:email,read:org
;auth_url = https://github.com/login/oauth/authorize
;token_url = https://github.com/login/oauth/access_token
;api_url = https://api.github.com/user
;team_ids =
;allowed_organizations =
#################################### Google Auth ##########################
[auth.google]
;enabled = false
;allow_sign_up = false
;client_id = some_client_id
;client_secret = some_client_secret
;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
;auth_url = https://accounts.google.com/o/oauth2/auth
;token_url = https://accounts.google.com/o/oauth2/token
;api_url = https://www.googleapis.com/oauth2/v1/userinfo
;allowed_domains =
#################################### Auth Proxy ##########################
[auth.proxy]
;enabled = false
;header_name = X-WEBAUTH-USER
;header_property = username
;auto_sign_up = true
#################################### Basic Auth ##########################
[auth.basic]
;enabled = true
#################################### Auth LDAP ##########################
[auth.ldap]
;enabled = false
;config_file = /etc/grafana/ldap.toml
#################################### SMTP / Emailing ##########################
[smtp]
;enabled = false
;host = localhost:25
;user =
;password =
;cert_file =
;key_file =
;skip_verify = false
;from_address = [email protected]
[emails]
;welcome_email_on_sign_up = false
#################################### Logging ##########################
[log]
# Either "console", "file", "syslog". Default is console and file
# Use space to separate multiple modes, e.g. "console file"
;mode = console, file
# Either "trace", "debug", "info", "warn", "error", "critical", default is "info"
;level = info
# For "console" mode only
[log.console]
;level =
# log line format, valid options are text, console and json
;format = console
# For "file" mode only
[log.file]
;level =
# log line format, valid options are text, console and json
;format = text
# This enables automated log rotate(switch of following options), default is true
;log_rotate = true
# Max line number of single file, default is 1000000
;max_lines = 1000000
# Max size shift of single file, default is 28 means 1 << 28, 256MB
;max_size_shift = 28
# Segment log daily, default is true
;daily_rotate = true
# Expired days of log file(delete after max days), default is 7
;max_days = 7
[log.syslog]
;level =
# log line format, valid options are text, console and json
;format = text
# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used.
;network =
;address =
# Syslog facility. user, daemon and local0 through local7 are valid.
;facility =
# Syslog tag. By default, the process' argv[0] is used.
;tag =
#################################### AMQP Event Publisher ##########################
[event_publisher]
;enabled = false
;rabbitmq_url = amqp://localhost/
;exchange = grafana_events
;#################################### Dashboard JSON files ##########################
[dashboards.json]
;enabled = false
;path = /var/lib/grafana/dashboards
#################################### Internal Grafana Metrics ##########################
# Metrics available at HTTP API Url /api/metrics
[metrics]
# Disable / Enable internal metrics
;enabled = true
# Publish interval
;interval_seconds = 10
# Send internal metrics to Graphite
; [metrics.graphite]
; address = localhost:2003
; prefix = prod.grafana.%(instance_name)s.
#################################### Internal Grafana Metrics ##########################
# Url used to to import dashboards directly from Grafana.net
[grafana_net]
url = https://grafana.net
+429
View File
@@ -0,0 +1,429 @@
{"dashboard":
{
"id": null,
"title": "StandaloneTest",
"originalTitle": "StandaloneTest",
"tags": [],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "Timely",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)",
"thresholdLine": false
},
"id": 1,
"legend": {
"alignAsTable": true,
"avg": false,
"current": true,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": true,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"isCounter": false,
"metric": "timely.metrics.received",
"refId": "A",
"shouldComputeRate": false
},
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"metric": "timely.keys.meta.inserted",
"refId": "B"
},
{
"aggregator": "sum",
"disableDownsampling": false,
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"metric": "timely.keys.metric.inserted",
"refId": "C"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Timely Status",
"tooltip": {
"msResolution": true,
"shared": false,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": "Keys",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"title": "Row"
},
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "Timely",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 2,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "$tag_host",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {
"host": ".*"
}
}
],
"timeFrom": null,
"timeShift": null,
"title": "CPU User by Host",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"datasource": "Timely",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 3,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "$tag_rack",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {
"rack": ".*"
}
}
],
"timeFrom": null,
"timeShift": null,
"title": "CPU User by Rack",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"datasource": "Timely",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 4,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "max",
"alias": "",
"currentTagKey": "",
"currentTagValue": "",
"disableDownsampling": false,
"downsampleAggregator": "max",
"downsampleFillPolicy": "none",
"downsampleInterval": "1s",
"metric": "sys.cpu0.user",
"refId": "A",
"tags": {}
}
],
"timeFrom": null,
"timeShift": null,
"title": "Cumulative CPU",
"tooltip": {
"msResolution": false,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
}
],
"title": "New row"
}
],
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"refresh": "5s",
"schemaVersion": 12,
"version": 1,
"links": []
}
}
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
# adapted from https://github.com/vishnubob/wait-for-it
cmdname=$(basename $0)
echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $TIMEOUT -gt 0 ]]; then
echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT"
else
echoerr "$cmdname: waiting for $HOST:$PORT without a timeout"
fi
start_ts=$(date +%s)
while :
do
(echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1
result=$?
if [[ $result -eq 0 ]]; then
end_ts=$(date +%s)
echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds"
break
fi
sleep 1
done
return $result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $QUIET -eq 1 ]]; then
timeout $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
else
timeout $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
fi
PID=$!
trap "kill -INT -$PID" INT
wait $PID
RESULT=$?
if [[ $RESULT -ne 0 ]]; then
echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT"
fi
return $RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
hostport=(${1//:/ })
HOST=${hostport[0]}
PORT=${hostport[1]}
shift 1
;;
--child)
CHILD=1
shift 1
;;
-q | --quiet)
QUIET=1
shift 1
;;
-s | --strict)
STRICT=1
shift 1
;;
-h)
HOST="$2"
if [[ $HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
HOST="${1#*=}"
shift 1
;;
-p)
PORT="$2"
if [[ $PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
PORT="${1#*=}"
shift 1
;;
-t)
TIMEOUT="$2"
if [[ $TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
CLI="$@"
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$HOST" == "" || "$PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
TIMEOUT=${TIMEOUT:-15}
STRICT=${STRICT:-0}
CHILD=${CHILD:-0}
QUIET=${QUIET:-0}
if [[ $CHILD -gt 0 ]]; then
wait_for
RESULT=$?
exit $RESULT
else
if [[ $TIMEOUT -gt 0 ]]; then
wait_for_wrapper
RESULT=$?
else
wait_for
RESULT=$?
fi
fi
if [[ $CLI != "" ]]; then
if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then
echoerr "$cmdname: strict mode, refusing to execute subprocess"
exit $RESULT
fi
exec $CLI
else
exit $RESULT
fi
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
else
THIS_SCRIPT=`readlink -f $0`
fi
THIS_DIR="${THIS_SCRIPT%/*}"
BASE_DIR=${THIS_DIR}/..
CONF_DIR="${BASE_DIR}/conf"
CONF_FILE="${CONF_DIR}/timely.properties"
LIB_DIR="${BASE_DIR}/lib"
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
JVM_ARGS="-Xmx256m -Xms128m -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.util.GetMetricTableSplitPoints "${CONF_FILE}""
$JAVA_HOME/bin/java ${JVM_ARGS} timely.util.GetMetricTableSplitPoints "${CONF_FILE}"
+16 -4
View File
@@ -12,8 +12,20 @@ CONF_DIR="${BASE_DIR}/conf"
CONF_FILE="${CONF_DIR}/timely.properties"
LIB_DIR="${BASE_DIR}/lib"
NUM_SERVER_THREADS=4
HOST="127.0.0.1"
PORT=54321
if [ -z ${TIMELY_HOST+x} ]; then
echo "TIMELY_HOST is unset, using 127.0.0.1"
TIMELY_HOST="127.0.0.1"
else
echo "TIMELY_HOST is set to '$TIMELY_HOST'"
fi
if [ -z ${TIMELY_PORT+x} ]; then
echo "TIMELY_PORT is unset, using 54321"
TIMELY_PORT=54321
else
echo "TIMELY_PORT is set to '$TIMELY_PORT'"
fi
CP=""
SEP=""
@@ -24,5 +36,5 @@ done
JVM_ARGS="-Xmx128m -Xms128m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
echo "$JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${HOST} ${PORT} --fast"
exec $JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${HOST} ${PORT} --fast
echo "$JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${TIMELY_HOST} ${TIMELY_PORT} --fast"
exec $JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${TIMELY_HOST} ${TIMELY_PORT} --fast
+16 -4
View File
@@ -12,8 +12,20 @@ CONF_DIR="${BASE_DIR}/conf"
CONF_FILE="${CONF_DIR}/timely.properties"
LIB_DIR="${BASE_DIR}/lib"
NUM_SERVER_THREADS=4
HOST="127.0.0.1"
PORT=54321
if [ -z ${TIMELY_HOST+x} ]; then
echo "TIMELY_HOST is unset, using 127.0.0.1"
TIMELY_HOST="127.0.0.1"
else
echo "TIMELY_HOST is set to '$TIMELY_HOST'"
fi
if [ -z ${TIMELY_PORT+x} ]; then
echo "TIMELY_PORT is unset, using 54321"
TIMELY_PORT=54321
else
echo "TIMELY_PORT is set to '$TIMELY_PORT'"
fi
CP=""
SEP=""
@@ -24,5 +36,5 @@ done
JVM_ARGS="-Xmx128m -Xms128m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
echo "$JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${HOST} ${PORT}"
exec $JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${HOST} ${PORT}
echo "$JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${TIMELY_HOST} ${TIMELY_PORT}"
exec $JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${TIMELY_HOST} ${TIMELY_PORT}
+11 -2
View File
@@ -2,10 +2,17 @@
if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
TCNATIVE_SUFFIX="tcnative.jnilib"
else
THIS_SCRIPT=`readlink -f $0`
TCNATIVE_SUFFIX="tcnative.so"
fi
# netty tcnative file reference
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
THIS_DIR="${THIS_SCRIPT%/*}"
NATIVE_DIR="${THIS_DIR}/META-INF/native"
BASE_DIR=${THIS_DIR}/..
@@ -25,12 +32,14 @@ if [[ -e ${NATIVE_DIR} ]]; then
fi
mkdir -p ${NATIVE_DIR}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty-tcnative.so
pushd ${BASE_DIR}/bin
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty-${TCNATIVE_SUFFIX}
popd
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}/libnetty-tcnative.so"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}/libnetty-${TCNATIVE_SUFFIX}"
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.Server "${CONF_FILE}""
$JAVA_HOME/bin/java ${JVM_ARGS} timely.Server "${CONF_FILE}"
+12 -2
View File
@@ -2,10 +2,17 @@
if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
TCNATIVE_SUFFIX="tcnative.jnilib"
else
THIS_SCRIPT=`readlink -f $0`
TCNATIVE_SUFFIX="tcnative.so"
fi
# netty tcnative file reference
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
THIS_DIR="${THIS_SCRIPT%/*}"
NATIVE_DIR="${THIS_DIR}/META-INF/native"
BASE_DIR=${THIS_DIR}/..
@@ -25,12 +32,15 @@ if [[ -e ${NATIVE_DIR} ]]; then
fi
mkdir -p ${NATIVE_DIR}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty-tcnative.so
pushd ${BASE_DIR}/bin
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty-${TCNATIVE_SUFFIX}
popd
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}/libnetty-tcnative.so"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
# for debugging
#JVM_ARGS="${JVM_ARGS} -agentlib:jdwp=transport=dt_socket,address=54323,server=y,suspend=y"
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.StandaloneServer "${CONF_FILE}" "${TMP_DIR}""
+12 -8
View File
@@ -1,6 +1,7 @@
timely.ip=127.0.0.1
timely.ip=0.0.0.0
timely.port.put=54321
timely.port.query=54322
timely.port.websocket=54323
timely.instance_name=TimelyStandalone
timely.zookeepers=127.0.0.1:9804
timely.username=root
@@ -22,14 +23,17 @@ timely.metrics.report.tags.ignored=
timely.meta.cache.expiration.minutes=60
timely.meta.cache.initial.capacity=2000
timely.meta.cache.max.capacity=10000
#timely.ssl.certificate.file=
#timely.ssl.key.file=
#timely.ssl.key.pass=
timely.ssl.use.generated.keypair=true
#timely.ssl.trust.store.file=
timely.ssl.certificate.file=/timely-server-src/bin/docker/grafana/pki/timely-cert.pem
timely.ssl.key.file=/timely-server-src/bin/docker/grafana/pki/timely-pkcs8-key.pem
timely.ssl.key.pass=timely
timely.ssl.use.generated.keypair=false
timely.ssl.trust.store.file=/timely-server-src/bin/docker/grafana/pki/timely-ca.pem
timely.ssl.use.openssl=true
#timely.ssl.use.ciphers=
timely.session.max.age=86400
timely.http.address=localhost
grafana.http.address=http://localhost:3000/login
timely.http.host=localhost
timely.allow.anonymous.access=true
timely.web.socket.timeout=60
timely.ws.subscription.lag=120
timely.http.redirect.path=/secure-me
timely.hsts.max.age=608400
+6 -2
View File
@@ -1,6 +1,7 @@
timely.ip=
timely.port.put=
timely.port.query=
timely.port.websocket=
timely.instance_name=
timely.zookeepers=
timely.username=
@@ -30,6 +31,9 @@ timely.ssl.trust.store.file=
timely.ssl.use.openssl=true
timely.ssl.use.ciphers=
timely.session.max.age=86400
timely.http.address=
grafana.http.address=
timely.http.host=
timely.allow.anonymous.access=false
timely.web.socket.timeout=60
timely.ws.subscription.lag=120
timely.http.redirect.path=/secure-me
timely.hsts.max.age=608400
+42
View File
@@ -0,0 +1,42 @@
version: '2.0'
services:
timely:
image: timely
build: .
ports:
- "9804:9804" # standalone zookeeper
- "54321:54321" # timely put
- "54322:54322" # timely query
volumes:
- ${PWD}/:/timely-server-src
command: /timely-server-src/bin/docker/deploy-startup-standalone.sh
environment:
- TIMELY_VERSION=0.0.2-SNAPSHOT
ingest:
image: timely
entrypoint: /timely-server-src/bin/docker/deploy-wait-and-insert.sh
volumes:
- ${PWD}/:/timely-server-src
depends_on:
- timely
environment:
- TIMELY_HOST=timely
- TIMELY_PORT=54321
grafana:
image: grafana/grafana
ports:
- "3000:3000"
volumes:
- ${PWD}/../grafana/timely-app:/var/lib/grafana/plugins/timely-app
- ${PWD}/bin/docker/grafana:/etc/grafana
entrypoint:
- gosu
- grafana
- /usr/sbin/grafana-server
- --homepath=/usr/share/grafana
- --config=/etc/grafana/grafana.ini
- cfg:default.paths.data=/var/lib/grafana
- cfg:default.paths.logs=/var/log/grafana
- cfg:default.paths.plugins=/var/lib/grafana/plugins
depends_on:
- timely
+31 -21
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.1</version>
<version>0.0.2</version>
</parent>
<dependencies>
<dependency>
@@ -38,6 +38,11 @@
<version>3.3.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.github.lukehutch</groupId>
<artifactId>fast-classpath-scanner</artifactId>
<version>1.10.4</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
@@ -45,7 +50,7 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId>
<version>1.1.33.Fork15</version>
<version>${version.netty-tcnative}</version>
<classifier>${os.detected.classifier}</classifier>
<!-- Eclipse doesn't like this, but it works on the cmd line -->
</dependency>
@@ -90,6 +95,12 @@
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<exclusions>
<exclusion>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
@@ -174,7 +185,7 @@
<artifactItem>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId>
<version>1.1.33.Fork15</version>
<version>${version.netty-tcnative}</version>
<classifier>${os.detected.classifier}</classifier>
<type>jar</type>
<overWrite>false</overWrite>
@@ -224,6 +235,17 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
@@ -295,12 +317,8 @@
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.revelc.code</groupId>
<artifactId>
formatter-maven-plugin
</artifactId>
<versionRange>
[0.5.2,)
</versionRange>
<artifactId>formatter-maven-plugin</artifactId>
<versionRange>[0.5.2,)</versionRange>
<goals>
<goal>format</goal>
</goals>
@@ -311,19 +329,11 @@
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-dependency-plugin
</artifactId>
<versionRange>
[2.10,)
</versionRange>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<versionRange>[2.10,)</versionRange>
<goals>
<goal>
copy-dependencies
</goal>
<goal>copy-dependencies</goal>
<goal>unpack</goal>
</goals>
</pluginExecutionFilter>
+7
View File
@@ -43,5 +43,12 @@
<include>*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/src/main/resources/webapp</directory>
<outputDirectory>bin/webapp</outputDirectory>
<includes>
<include>*</include>
</includes>
</fileSet>
</fileSets>
</assembly>
Loaded 100 of 224 files, more files were not shown because too many files have changed in this diff. Show more