Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
053d15c559 | ||
|
|
b3a479636b | ||
|
|
8539986faf | ||
|
|
cbdf28c510 | ||
|
|
670cc7769d | ||
|
|
22a9be3ba2 | ||
|
|
2836b03366 | ||
|
|
64bde2189e | ||
|
|
4d41c8e19a | ||
|
|
08a095e349 | ||
|
|
fc0f2b8356 | ||
|
|
e9c0f63478 | ||
|
|
9c01d00686 | ||
|
|
dd8d379292 | ||
|
|
1acd5ce5cb | ||
|
|
aa3ac901b6 | ||
|
|
2c6270f23e | ||
|
|
f6728b4e8d | ||
|
|
fb0590d778 | ||
|
|
f94ec78c66 | ||
|
|
3fddbb4c6f | ||
|
|
e841ac90b8 | ||
|
|
203a97d7db | ||
|
|
4d19538c77 | ||
|
|
e83356cb74 | ||
|
|
5f1beb6045 | ||
|
|
12ee77386b | ||
|
|
accbb2af9a | ||
|
|
041cee1a80 | ||
|
|
f8efdbf240 | ||
|
|
ca4155be6f | ||
|
|
b3c7b143e0 | ||
|
|
42d31efff8 | ||
|
|
56e90ef693 | ||
|
|
6345efa996 | ||
|
|
701f841cfe | ||
|
|
4b6de88e25 | ||
|
|
de9b0df49e | ||
|
|
b8418e4e19 | ||
|
|
d5cec191e2 | ||
|
|
13feefde31 | ||
|
|
86c9d8835d | ||
|
|
b8da5152e0 | ||
|
|
e73564c578 | ||
|
|
f42696ddfc | ||
|
|
7e3a85050d |
No files matched your search
@@ -1,180 +1,8 @@
|
||||
# Timely
|
||||
---
|
||||

|
||||
|
||||
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
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
|
||||
@@ -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'
|
||||
]);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
# Timely
|
||||
|
||||
Timely: a scalable, distributed, **secure** time series database.
|
||||
@@ -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>
|
||||
@@ -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,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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"]}
|
||||
@@ -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,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
|
||||
@@ -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"]}
|
||||
@@ -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,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
|
||||
@@ -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"]}
|
||||
@@ -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}} = {{fil.type}}({{fil.filter}}) , groupBy = {{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}} = {{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>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.timely-app-heading {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../src/datasource/datasource.js"],"names":[],"mappings":"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 truncated
|
||||
@@ -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
|
||||
@@ -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"]}
|
||||
@@ -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,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
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -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
|
||||
@@ -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"]}
|
||||
@@ -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": []
|
||||
}
|
||||
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -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"
|
||||
}
|
||||
@@ -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}} = {{fil.type}}({{fil.filter}}) , groupBy = {{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}} = {{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>
|
||||
|
||||
@@ -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';
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -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
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
/timely-server-src/bin/docker/deploy-timely-build.sh
|
||||
/timely/bin/timely-standalone.sh
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pki/
|
||||
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}""
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||