Move monasca datasource to new repo
The previous repo was https://github.com/twc-openstack/grafana-plugins Change-Id: I07ce789fb7455a8969fa9c6e8bcc78a09de1c0b3
This commit is contained in:
parent
e03a9c1444
commit
eb7b0c6c89
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {2016} {Raintank Inc.}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
## Monasca Datasource - A datasource for use with the OpenStack Monasca api.
|
||||||
|
|
||||||
|
For more information on Monasca see the [Monasca documentation](https://wiki.openstack.org/wiki/Monasca)
|
||||||
|
|
||||||
|
When combined with Grafana Keystone authentication this datasource supports using login credentials to authenticate queries.
|
529
datasource.js
Normal file
529
datasource.js
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
define([
|
||||||
|
'angular',
|
||||||
|
'lodash',
|
||||||
|
'moment',
|
||||||
|
'app/plugins/sdk',
|
||||||
|
'app/core/utils/datemath',
|
||||||
|
'app/core/utils/kbn',
|
||||||
|
'./query_ctrl',
|
||||||
|
],
|
||||||
|
function (angular, _, moment, sdk, dateMath, kbn) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var self;
|
||||||
|
|
||||||
|
function MonascaDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||||
|
this.url = instanceSettings.url;
|
||||||
|
this.name = instanceSettings.name;
|
||||||
|
|
||||||
|
if (instanceSettings.jsonData) {
|
||||||
|
this.token = instanceSettings.jsonData.token;
|
||||||
|
this.keystoneAuth = instanceSettings.jsonData.keystoneAuth;
|
||||||
|
} else {
|
||||||
|
this.token = null;
|
||||||
|
this.keystoneAuth = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.q = $q;
|
||||||
|
this.backendSrv = backendSrv;
|
||||||
|
this.templateSrv = templateSrv;
|
||||||
|
|
||||||
|
self = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.query = function(options) {
|
||||||
|
var datasource = this;
|
||||||
|
var from = this.translateTime(options.range.from);
|
||||||
|
var to = this.translateTime(options.range.to);
|
||||||
|
|
||||||
|
var targets_list = [];
|
||||||
|
for (var i = 0; i < options.targets.length; i++) {
|
||||||
|
var target = options.targets[i];
|
||||||
|
// Missing target.period indicates a new unfilled query
|
||||||
|
if (target.error || target.hide || !target.period) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var query = this.buildDataQuery(options.targets[i], from, to);
|
||||||
|
query = self.templateSrv.replace(query, options.scopedVars);
|
||||||
|
var query_list
|
||||||
|
if (options.group){
|
||||||
|
query_list = this.expandTemplatedQueries(query);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
query_list = this.expandQueries(query);
|
||||||
|
}
|
||||||
|
targets_list.push(query_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
var targets_promise = self.q.all(targets_list).then(function(results) {
|
||||||
|
return _.flatten(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
var promises = self.q.resolve(targets_promise).then(function(targets) {
|
||||||
|
return targets.map(function (target) {
|
||||||
|
target = datasource.convertPeriod(target);
|
||||||
|
return datasource._limitedMonascaRequest(target, {}).then(datasource.convertDataPoints).catch(function(err) {throw err});
|
||||||
|
});
|
||||||
|
}).catch(function(err) {throw err});
|
||||||
|
|
||||||
|
return self.q.resolve(promises).then(function(promises) {
|
||||||
|
return self.q.all(promises).then(function(results) {
|
||||||
|
var sorted_results = results.map(function (results) {
|
||||||
|
return results.sort(function (a, b) {
|
||||||
|
return a.target.localeCompare(b.target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return { data: _.flatten(sorted_results).filter(function(result) { return !_.isEmpty(result)}) };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.namesQuery = function() {
|
||||||
|
return this._limitedMonascaRequest('/v2.0/metrics/names', {}).catch(function(err) {throw err});
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.convertNamesList = function(data) {
|
||||||
|
var metrics = [];
|
||||||
|
data = data.data.elements;
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
metrics.push(data[i].name);
|
||||||
|
}
|
||||||
|
return metrics;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.metricsQuery = function(params) {
|
||||||
|
return this._limitedMonascaRequest('/v2.0/metrics', params).catch(function(err) {throw err});
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.buildDimensionList = function(data) {
|
||||||
|
var keys = [];
|
||||||
|
var values = {};
|
||||||
|
data = data.data.elements;
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
var dim_set = data[i].dimensions;
|
||||||
|
for (var key in dim_set) {
|
||||||
|
if (keys.indexOf(key) == -1) {
|
||||||
|
keys.push(key);
|
||||||
|
values[key] = [];
|
||||||
|
}
|
||||||
|
var value = dim_set[key];
|
||||||
|
if (values[key].indexOf(value) == -1) {
|
||||||
|
values[key].push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {'keys' : keys, 'values' : values};
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.buildMetricList = function(data) {
|
||||||
|
data = data.data.elements;
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.buildDataQuery = function(options, from, to) {
|
||||||
|
var params = {};
|
||||||
|
params.name = options.metric;
|
||||||
|
if (options.group) {
|
||||||
|
params.group_by = '*';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params.merge_metrics = 'true';
|
||||||
|
}
|
||||||
|
params.start_time = from;
|
||||||
|
if (to) {
|
||||||
|
params.end_time = to;
|
||||||
|
}
|
||||||
|
if (options.dimensions) {
|
||||||
|
var dimensions = '';
|
||||||
|
for (var i = 0; i < options.dimensions.length; i++) {
|
||||||
|
var key = options.dimensions[i].key;
|
||||||
|
var value = options.dimensions[i].value;
|
||||||
|
if (options.group && value == '$all') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (dimensions) {
|
||||||
|
dimensions += ',';
|
||||||
|
}
|
||||||
|
dimensions += key;
|
||||||
|
dimensions += ':';
|
||||||
|
dimensions += value;
|
||||||
|
}
|
||||||
|
params.dimensions = dimensions;
|
||||||
|
}
|
||||||
|
if (options.alias) {
|
||||||
|
params.alias = options.alias;
|
||||||
|
}
|
||||||
|
var path;
|
||||||
|
if (options.aggregator != 'none') {
|
||||||
|
params.statistics = options.aggregator;
|
||||||
|
params.period = options.period;
|
||||||
|
path = '/v2.0/metrics/statistics';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
path = '/v2.0/metrics/measurements';
|
||||||
|
}
|
||||||
|
var first = true;
|
||||||
|
Object.keys(params).forEach(function (key) {
|
||||||
|
if (first) {
|
||||||
|
path += '?';
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
path += '&';
|
||||||
|
}
|
||||||
|
path += key;
|
||||||
|
path += '=';
|
||||||
|
path += params[key];
|
||||||
|
});
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.expandQueries = function(query) {
|
||||||
|
var datasource = this;
|
||||||
|
return this.expandAllQueries(query).then(function(partial_query_list) {
|
||||||
|
var query_list = [];
|
||||||
|
for (var i = 0; i < partial_query_list.length; i++) {
|
||||||
|
query_list = query_list.concat(datasource.expandTemplatedQueries(partial_query_list[i]));
|
||||||
|
}
|
||||||
|
query_list = datasource.autoAlias(query_list);
|
||||||
|
return query_list;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.expandTemplatedQueries = function(query) {
|
||||||
|
var templated_vars = query.match(/{[^}]*}/g);
|
||||||
|
if (!templated_vars) {
|
||||||
|
return [query];
|
||||||
|
}
|
||||||
|
|
||||||
|
var expandedQueries = [];
|
||||||
|
var to_replace = templated_vars[0];
|
||||||
|
var var_options = to_replace.substring(1, to_replace.length - 1);
|
||||||
|
var_options = var_options.split(',');
|
||||||
|
for (var i = 0; i < var_options.length; i++) {
|
||||||
|
var new_query = query.replace(new RegExp(to_replace, 'g'), var_options[i]);
|
||||||
|
expandedQueries = expandedQueries.concat(this.expandTemplatedQueries(new_query));
|
||||||
|
}
|
||||||
|
return expandedQueries;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.expandAllQueries = function(query) {
|
||||||
|
if (query.indexOf("$all") > -1) {
|
||||||
|
var metric_name = query.match(/name=([^&]*)/)[1];
|
||||||
|
var start_time = query.match(/start_time=([^&]*)/)[1];
|
||||||
|
|
||||||
|
// Find all matching subqueries
|
||||||
|
var dimregex = /(?:dimensions=|,)([^,]*):\$all/g;
|
||||||
|
var matches, neededDimensions = [];
|
||||||
|
while (matches = dimregex.exec(query)) {
|
||||||
|
neededDimensions.push(matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var metricQueryParams = {'name' : metric_name, 'start_time': start_time};
|
||||||
|
var queriesPromise = this.metricsQuery(metricQueryParams).then(function(data) {
|
||||||
|
var expandedQueries = [];
|
||||||
|
var metrics = data.data.elements;
|
||||||
|
var matchingMetrics = {}; // object ensures uniqueness of dimension sets
|
||||||
|
for (var i = 0; i < metrics.length; i++) {
|
||||||
|
var dimensions = metrics[i].dimensions;
|
||||||
|
var set = {};
|
||||||
|
var skip = false;
|
||||||
|
for (var j = 0; j < neededDimensions.length; j++) {
|
||||||
|
var key = neededDimensions[j];
|
||||||
|
if (!(key in dimensions)) {
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
set[key] = dimensions[key];
|
||||||
|
}
|
||||||
|
if (!skip) {
|
||||||
|
matchingMetrics[JSON.stringify(set)] = set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.keys(matchingMetrics).forEach(function (set) {
|
||||||
|
var new_query = query;
|
||||||
|
var match = matchingMetrics[set];
|
||||||
|
Object.keys(match).forEach(function (key) {
|
||||||
|
var to_replace = key+":\\$all";
|
||||||
|
var replacement = key+":"+match[key];
|
||||||
|
new_query = new_query.replace(new RegExp(to_replace, 'g'), replacement);
|
||||||
|
});
|
||||||
|
expandedQueries.push(new_query);
|
||||||
|
});
|
||||||
|
return expandedQueries;
|
||||||
|
});
|
||||||
|
|
||||||
|
return queriesPromise;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return self.q.resolve([query]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Alias based on dimensions in query
|
||||||
|
// Used when querying with merge flag, where no dimension info is returned.
|
||||||
|
MonascaDatasource.prototype.autoAlias = function(query_list) {
|
||||||
|
function keysSortedByLengthDesc(obj) {
|
||||||
|
var keys = [];
|
||||||
|
for (var key in obj) {
|
||||||
|
keys.push(key)
|
||||||
|
}
|
||||||
|
function byLength(a, b) {return b.length - a.length}
|
||||||
|
return keys.sort(byLength)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < query_list.length; i++) {
|
||||||
|
var query = query_list[i];
|
||||||
|
var alias = query.match(/alias=[^&@]*@([^&]*)/);
|
||||||
|
var dimensions = query.match(/dimensions=([^&]*)/);
|
||||||
|
if (alias && dimensions[1]) {
|
||||||
|
var dimensions_list = dimensions[1].split(',');
|
||||||
|
var dimensions_dict = {};
|
||||||
|
for (var j = 0; j < dimensions_list.length; j++) {
|
||||||
|
var dim = dimensions_list[j].split(':');
|
||||||
|
dimensions_dict[dim[0]] = dim[1];
|
||||||
|
}
|
||||||
|
var keys = keysSortedByLengthDesc(dimensions_dict);
|
||||||
|
for (var k in keys) {
|
||||||
|
query = query.replace(new RegExp("@"+keys[k], 'g'), dimensions_dict[keys[k]]);
|
||||||
|
}
|
||||||
|
query_list[i] = query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return query_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.convertDataPoints = function(data) {
|
||||||
|
function keysSortedByLengthDesc(obj) {
|
||||||
|
var keys = [];
|
||||||
|
for (var key in obj) {
|
||||||
|
keys.push(key)
|
||||||
|
}
|
||||||
|
function byLength(a, b) {return b.length - a.length}
|
||||||
|
return keys.sort(byLength)
|
||||||
|
};
|
||||||
|
|
||||||
|
var url = data.config.url;
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < data.data.elements.length; i++)
|
||||||
|
{
|
||||||
|
var element = data.data.elements[i];
|
||||||
|
|
||||||
|
var target = element.name;
|
||||||
|
var alias = data.config.url.match(/alias=([^&]*)/);
|
||||||
|
// Alias based on returned dimensions
|
||||||
|
// Used when querying with group_by flag where dimensions are not specified in initial query
|
||||||
|
if (alias) {
|
||||||
|
alias = alias[1];
|
||||||
|
var keys = keysSortedByLengthDesc(element.dimensions);
|
||||||
|
for (var k in keys)
|
||||||
|
{
|
||||||
|
alias = alias.replace(new RegExp("@"+keys[k], 'g'), element.dimensions[keys[k]])
|
||||||
|
}
|
||||||
|
target = alias
|
||||||
|
}
|
||||||
|
|
||||||
|
var raw_datapoints;
|
||||||
|
var aggregator;
|
||||||
|
if ('measurements' in element) {
|
||||||
|
raw_datapoints = element.measurements;
|
||||||
|
aggregator = 'value';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
raw_datapoints = element.statistics;
|
||||||
|
aggregator = url.match(/statistics=[^&]*/);
|
||||||
|
aggregator = aggregator[0].substring('statistics='.length);
|
||||||
|
}
|
||||||
|
var datapoints = [];
|
||||||
|
var timeCol = element.columns.indexOf('timestamp');
|
||||||
|
var dataCol = element.columns.indexOf(aggregator);
|
||||||
|
for (var j = 0; j < raw_datapoints.length; j++) {
|
||||||
|
var datapoint = raw_datapoints[j];
|
||||||
|
var time = new Date(datapoint[timeCol]);
|
||||||
|
var point = datapoint[dataCol];
|
||||||
|
datapoints.push([point, time.getTime()]);
|
||||||
|
}
|
||||||
|
var convertedData = { 'target': target, 'datapoints': datapoints };
|
||||||
|
results.push(convertedData)
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
// For use with specified or api enforced limits.
|
||||||
|
// Pages through data until all data is retrieved.
|
||||||
|
MonascaDatasource.prototype._limitedMonascaRequest = function(path, params) {
|
||||||
|
var datasource = this;
|
||||||
|
var deferred = self.q.defer();
|
||||||
|
var data = null;
|
||||||
|
var element_list = [];
|
||||||
|
|
||||||
|
function aggregateResults() {
|
||||||
|
var elements = {};
|
||||||
|
for (var i = 0; i < element_list.length; i++) {
|
||||||
|
var element = element_list[i];
|
||||||
|
if (element.id in elements){
|
||||||
|
if (element.measurements){
|
||||||
|
elements[element.id].measurements = elements[element.id].measurements.concat(element.measurements);
|
||||||
|
}
|
||||||
|
if (element.statistics){
|
||||||
|
elements[element.id].measurements = elements[element.id].statistics.concat(element.statistics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
elements[element.id] = element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.data.elements = Object.keys(elements).map(function(key) {
|
||||||
|
return elements[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle incosistent element.id from merging here. Remove when this bug is fixed.
|
||||||
|
function flattenResults() {
|
||||||
|
var elements = [];
|
||||||
|
for (var i = 0; i < element_list.length; i++) {
|
||||||
|
var element = element_list[i];
|
||||||
|
if (element.measurements){
|
||||||
|
elements.push(element.measurements);
|
||||||
|
}
|
||||||
|
if (element.statistics){
|
||||||
|
elements.push(element.statistics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.data.elements[0].measurements){
|
||||||
|
data.data.elements[0].measurements = _.flatten(elements, true)
|
||||||
|
}
|
||||||
|
if (data.data.elements[0].statistics){
|
||||||
|
data.data.elements[0].statistics = _.flatten(elements, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestAll(multi_page){
|
||||||
|
datasource._monascaRequest(path, params)
|
||||||
|
.then(function(d) {
|
||||||
|
data = d
|
||||||
|
element_list = element_list.concat(d.data.elements);
|
||||||
|
if(d.data.links) {
|
||||||
|
for (var i = 0; i < d.data.links.length; i++) {
|
||||||
|
if (d.data.links[i].rel == 'next'){
|
||||||
|
var next = decodeURIComponent(d.data.links[i].href)
|
||||||
|
var offset = next.match(/offset=([^&]*)/);
|
||||||
|
params.offset = offset[1];
|
||||||
|
requestAll(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle incosistent element.id from merging here. Remove when this bug is fixed.
|
||||||
|
var query = d.data.links[0].href
|
||||||
|
if (multi_page){
|
||||||
|
if (query.indexOf('merge_metrics') > -1) {
|
||||||
|
flattenResults();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
aggregateResults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deferred.resolve(data);
|
||||||
|
}).catch(function(err) {deferred.reject(err)});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAll(false);
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype._monascaRequest = function(path, params) {
|
||||||
|
var headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Token': this.token
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: this.url + path,
|
||||||
|
params: params,
|
||||||
|
headers: headers,
|
||||||
|
withCredentials: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.backendSrv.datasourceRequest(options).catch(function(err) {
|
||||||
|
if (err.status !== 0 || err.status >= 300) {
|
||||||
|
var monasca_response
|
||||||
|
if (err.data) {
|
||||||
|
if (err.data.message){
|
||||||
|
monasca_response = err.data.message;
|
||||||
|
} else{
|
||||||
|
var err_name = Object.keys(err.data)[0]
|
||||||
|
monasca_response = err.data[err_name].message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (monasca_response) {
|
||||||
|
throw { message: 'Monasca Error Response: ' + monasca_response };
|
||||||
|
} else {
|
||||||
|
throw { message: 'Monasca Error Status: ' + err.status };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.metricFindQuery = function(query) {
|
||||||
|
return this.metricsQuery({}).then(function(data) {
|
||||||
|
var values = [];
|
||||||
|
data = data.data.elements;
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
var dim_set = data[i].dimensions;
|
||||||
|
if (query in dim_set) {
|
||||||
|
var value = dim_set[query];
|
||||||
|
if (values.indexOf(value) == -1) {
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _.map(values, function(value) {
|
||||||
|
return {text: value};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.listTemplates = function() {
|
||||||
|
var template_list = [];
|
||||||
|
for (var i = 0; i < self.templateSrv.variables.length; i++) {
|
||||||
|
template_list.push('$'+self.templateSrv.variables[i].name);
|
||||||
|
}
|
||||||
|
return template_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.testDatasource = function() {
|
||||||
|
return this.namesQuery().then(function () {
|
||||||
|
return { status: 'success', message: 'Data source is working', title: 'Success' };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.translateTime = function(date) {
|
||||||
|
if (date === 'now') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return moment.utc(dateMath.parse(date).valueOf()).toISOString();
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.convertPeriod = function(target) {
|
||||||
|
var regex = target.match(/period=[^&]*/);
|
||||||
|
if (regex) {
|
||||||
|
var period = regex[0].substring('period='.length);
|
||||||
|
var matches = period.match(kbn.interval_regex);
|
||||||
|
if (matches) {
|
||||||
|
period = kbn.interval_to_seconds(period);
|
||||||
|
target = target.replace(regex, 'period='+period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaDatasource.prototype.isInt = function(str) {
|
||||||
|
var n = ~~Number(str);
|
||||||
|
return String(n) === str && n >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
return MonascaDatasource;
|
||||||
|
});
|
17
directives.js
Normal file
17
directives.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
define([
|
||||||
|
'angular',
|
||||||
|
],
|
||||||
|
function (angular) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var module = angular.module('grafana.directives');
|
||||||
|
|
||||||
|
module.directive('metricQueryEditorMonasca', function() {
|
||||||
|
return {controller: 'MonascaQueryCtrl', templateUrl: 'app/plugins/datasource/monasca/partials/query.editor.html'};
|
||||||
|
});
|
||||||
|
|
||||||
|
module.directive('metricQueryOptionsMonasca', function() {
|
||||||
|
return {templateUrl: 'app/plugins/datasource/monasca/partials/query.options.html'};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
BIN
img/openstack_logo.png
Normal file
BIN
img/openstack_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
20
module.js
Normal file
20
module.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
define([
|
||||||
|
'./datasource',
|
||||||
|
'./query_ctrl'
|
||||||
|
],
|
||||||
|
function(MonascaDatasource, MonascaQueryCtrl) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MonascaConfigCtrl = function() {};
|
||||||
|
MonascaConfigCtrl.templateUrl = "partials/config.html";
|
||||||
|
|
||||||
|
var MonascaQueryOptionsCtrl = function() {};
|
||||||
|
MonascaQueryOptionsCtrl.templateUrl = "partials/query.options.html";
|
||||||
|
|
||||||
|
return {
|
||||||
|
'Datasource': MonascaDatasource,
|
||||||
|
'QueryCtrl': MonascaQueryCtrl,
|
||||||
|
'ConfigCtrl': MonascaConfigCtrl,
|
||||||
|
'QueryOptionsCtrl': MonascaQueryOptionsCtrl
|
||||||
|
};
|
||||||
|
});
|
55
partials/config.html
Normal file
55
partials/config.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<div class="gf-form-group">
|
||||||
|
<h3 class="page-heading">Http settings</h3>
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form max-width-30">
|
||||||
|
<span class="gf-form-label width-7">Url</span>
|
||||||
|
<input class="gf-form-input" type="text" ng-model='ctrl.current.url' placeholder="for example: http://localhost:8081" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
|
||||||
|
<info-popover mode="right-absolute">
|
||||||
|
<p>Specify a complete HTTP url (for example http://your_server:8080)</p>
|
||||||
|
<span ng-show="ctrl.current.access === 'direct'">
|
||||||
|
Your access method is <em>Direct</em>, this means the url
|
||||||
|
needs to be accessable from the browser.
|
||||||
|
</span>
|
||||||
|
<span ng-show="ctrl.current.access === 'proxy'">
|
||||||
|
Your access method is currently <em>Proxy</em>, this means the url
|
||||||
|
needs to be accessable from the grafana backend.
|
||||||
|
</span>
|
||||||
|
</info-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form max-width-30">
|
||||||
|
<span class="gf-form-label width-7">Access</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.current.access" ng-options="f for f in ['direct', 'proxy']"></select>
|
||||||
|
<info-popover mode="right-absolute">
|
||||||
|
Direct = url is used directly from browser<br>
|
||||||
|
Proxy = Grafana backend will proxy the request
|
||||||
|
</info-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form-group">
|
||||||
|
<h3 class="page-heading">Monasca details</h3>
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form max-width-30">
|
||||||
|
<span class="gf-form-label width-7">Token</span>
|
||||||
|
<input class="gf-form-input" type="text" ng-model='ctrl.current.jsonData.token' placeholder=""
|
||||||
|
ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/"></input>
|
||||||
|
<info-popover mode="right-absolute">
|
||||||
|
<span>A token is required to autenticate if Keystone auth is not set.</span>
|
||||||
|
</info-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form max-width-30">
|
||||||
|
<span class="gf-form-label width-7">Auth</span>
|
||||||
|
<gf-form-switch class="gf-form" label="Keystone Auth"
|
||||||
|
checked="ctrl.current.jsonData.keystoneAuth" switch-class="max-width-6">
|
||||||
|
</gf-form-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
99
partials/query.editor.html
Normal file
99
partials/query.editor.html
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<query-editor-row query-ctrl="ctrl" can-collapse="false">
|
||||||
|
<div>
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<label class="gf-form-label query-keyword width-7">Metric</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="gf-form-input"
|
||||||
|
ng-model="ctrl.target.metric"
|
||||||
|
spellcheck='false'
|
||||||
|
placeholder="Metric Name"
|
||||||
|
bs-typeahead="ctrl.suggestMetrics"
|
||||||
|
data-min-length=0
|
||||||
|
ng-blur="ctrl.onMetricChange()" >
|
||||||
|
</div>
|
||||||
|
<div class="gf-form max-width-15">
|
||||||
|
<label class="gf-form-label query-keyword">Alias</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="gf-form-input"
|
||||||
|
ng-model="ctrl.target.alias"
|
||||||
|
spellcheck='false'
|
||||||
|
placeholder="Alias"
|
||||||
|
bs-typeahead="ctrl.suggestAlias"
|
||||||
|
data-min-length=0
|
||||||
|
ng-blur="ctrl.targetBlur()" >
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label query-keyword width-7">Dimensions</label>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form" ng-repeat="dimension in ctrl.target.dimensions track by $index">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="gf-form-input max-width-7"
|
||||||
|
placeholder="key"
|
||||||
|
ng-model="dimension.key"
|
||||||
|
bs-typeahead="ctrl.suggestDimensionKeys"
|
||||||
|
ng-blur="ctrl.targetBlur()"
|
||||||
|
data-min-length=0
|
||||||
|
style="margin-right:0">
|
||||||
|
<label class="gf-form-label query-segment-operator" style="margin-right:0">=</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="gf-form-input max-width-7"
|
||||||
|
placeholder="value"
|
||||||
|
ng-model="dimension.value"
|
||||||
|
bs-typeahead="ctrl.suggestDimensionValues"
|
||||||
|
ng-focus="ctrl.editDimension($index)"
|
||||||
|
ng-blur="ctrl.targetBlur()"
|
||||||
|
data-min-length=0
|
||||||
|
style="margin-right:0">
|
||||||
|
<label class="gf-form-label"">
|
||||||
|
<a class="pointer" ng-click="ctrl.removeDimension($index)"><i class="fa fa-close"></i></a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label">
|
||||||
|
<a class="pointer" ng-click="ctrl.addDimension()"><i class="fa fa-plus"></i></a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
|
</div>
|
||||||
|
<gf-form-switch
|
||||||
|
class="gf-form"
|
||||||
|
label-class="gf-form-label query-keyword"
|
||||||
|
label="Group_by"
|
||||||
|
checked="ctrl.target.group"
|
||||||
|
on-change="ctrl.targetBlur()"
|
||||||
|
switch-class="max-width-6"></gf-form-switch>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label query-keyword width-7">Function</label>
|
||||||
|
<div class="gf-form-select-wrapper">
|
||||||
|
<select
|
||||||
|
class="gf-form-input"
|
||||||
|
ng-model="ctrl.target.aggregator"
|
||||||
|
ng-blur="ctrl.targetBlur()"
|
||||||
|
ng-options="f for f in ['none','count', 'avg', 'sum', 'min', 'max']">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form" ng-hide="ctrl.target.aggregator=='none'">
|
||||||
|
<label class="gf-form-label query-keyword">Period</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="gf-form-input"
|
||||||
|
ng-model="ctrl.target.period"
|
||||||
|
ng-blur="ctrl.targetBlur()">
|
||||||
|
</div>
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</query-editor-row>
|
52
partials/query.options.html
Normal file
52
partials/query.options.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<section class="grafana-metric-options gf-form-group">
|
||||||
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label width-auto">
|
||||||
|
<i class="fa fa-info-circle"></i>
|
||||||
|
<a ng-click="ctrl.panelCtrl.toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||||
|
Dimension Templating
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="gf-form-label width-auto">
|
||||||
|
<i class="fa fa-info-circle"></i>
|
||||||
|
<a ng-click="ctrl.panelCtrl.toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||||
|
Aliasing
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="gf-form-label width-auto">
|
||||||
|
<i class="fa fa-info-circle"></i>
|
||||||
|
<a ng-click="ctrl.panelCtrl.toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||||
|
Group By Time
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="pull-left">
|
||||||
|
<div class="grafana-info-box" style="border: 0;" ng-if="ctrl.panelCtrl.editorHelpIndex === 1">
|
||||||
|
<h5>Dimensions Templating</h5>
|
||||||
|
<ul ng-non-bindable>
|
||||||
|
<li>Template variables can be used as normal</li>
|
||||||
|
<li>$all : Use as a dimension value to automatically group the query by all valid values for the dimension</li>
|
||||||
|
<li>When 'group_by' is selected, $all will be ignored, and the query will group the data on all unspecified dimensions</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grafana-info-box" style="border: 0;" ng-if="ctrl.panelCtrl.editorHelpIndex === 2">
|
||||||
|
<h5>Aliasing</h5>
|
||||||
|
<ul ng-non-bindable>
|
||||||
|
<li>Template variables can be used as normal</li>
|
||||||
|
<li>@dimension_name : Use to alias using the given dimension_name</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grafana-info-box" style="border: 0;" ng-if="ctrl.panelCtrl.editorHelpIndex === 3">
|
||||||
|
<h5>Period</h5>
|
||||||
|
<ul ng-non-bindable>
|
||||||
|
<li>Required when function is not none</li>
|
||||||
|
<li>Defaults to seconds if s,m,h,d is not specified</li>
|
||||||
|
<li>Works with grafana interval templates</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
37
plugin.json
Normal file
37
plugin.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "Monasca",
|
||||||
|
"id": "monasca-grafana-datasource",
|
||||||
|
"type": "datasource",
|
||||||
|
|
||||||
|
"staticRoot": ".",
|
||||||
|
|
||||||
|
"partials": {
|
||||||
|
"config": "app/plugins/datasource/monasca/partials/config.html"
|
||||||
|
},
|
||||||
|
|
||||||
|
"metrics": true,
|
||||||
|
"annotations": false,
|
||||||
|
|
||||||
|
"info": {
|
||||||
|
"description": "datasource for the Monasca Api",
|
||||||
|
"author": {
|
||||||
|
"name": "OpenStack",
|
||||||
|
"url": "https://wiki.openstack.org/wiki/Monasca"
|
||||||
|
},
|
||||||
|
"logos": {
|
||||||
|
"small": "img/openstack_logo.png",
|
||||||
|
"large": "img/openstack_logo.png"
|
||||||
|
},
|
||||||
|
"links": [
|
||||||
|
{"name": "GitHub", "url": "https://github.com/openstack/monasca-grafana-datasource"},
|
||||||
|
{"name": "License", "url": "https://github.com/openstack/monasca-grafana-datasource/LICENSE"}
|
||||||
|
],
|
||||||
|
"version": "1.0.0",
|
||||||
|
"updated": "2016-07-26"
|
||||||
|
},
|
||||||
|
|
||||||
|
"dependencies": {
|
||||||
|
"grafanaVersion": "3.x.x",
|
||||||
|
"plugins": [ ]
|
||||||
|
}
|
||||||
|
}
|
171
query_ctrl.js
Normal file
171
query_ctrl.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
define([
|
||||||
|
'angular',
|
||||||
|
'lodash',
|
||||||
|
'app/plugins/sdk'
|
||||||
|
],
|
||||||
|
function (angular, _, sdk) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MonascaQueryCtrl = (function(_super) {
|
||||||
|
|
||||||
|
var self;
|
||||||
|
var metricList = null;
|
||||||
|
var dimensionList = { 'keys' : [], 'values' : {} };
|
||||||
|
var currentDimension = null;
|
||||||
|
|
||||||
|
function MonascaQueryCtrl($scope, $injector, templateSrv, $q, uiSegmentSrv) {
|
||||||
|
_super.call(this, $scope, $injector);
|
||||||
|
this.q = $q;
|
||||||
|
this.uiSegmentSrv = uiSegmentSrv;
|
||||||
|
this.templateSrv = templateSrv;
|
||||||
|
|
||||||
|
if (!this.target.aggregator) {
|
||||||
|
this.target.aggregator = 'avg';
|
||||||
|
}
|
||||||
|
if (!this.target.period) {
|
||||||
|
this.target.period = '300';
|
||||||
|
}
|
||||||
|
if (!this.target.dimensions) {
|
||||||
|
this.target.dimensions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.validateTarget();
|
||||||
|
if (this.target.metric) {
|
||||||
|
this.resetDimensionList();
|
||||||
|
}
|
||||||
|
|
||||||
|
self = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype = Object.create(_super.prototype);
|
||||||
|
MonascaQueryCtrl.prototype.constructor = MonascaQueryCtrl;
|
||||||
|
|
||||||
|
MonascaQueryCtrl.templateUrl = 'partials/query.editor.html';
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.targetBlur = function() {
|
||||||
|
this.validateTarget();
|
||||||
|
if (!_.isEqual(this.oldTarget, this.target) && _.isEmpty(this.target.error)) {
|
||||||
|
this.oldTarget = angular.copy(this.target);
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.validateTarget = function() {
|
||||||
|
this.target.error = "";
|
||||||
|
if (!this.target.metric) {
|
||||||
|
this.target.error = "No metric specified";
|
||||||
|
}
|
||||||
|
if (this.target.aggregator != 'none' && !this.target.period) {
|
||||||
|
this.target.error = "You must supply a period when using an aggregator";
|
||||||
|
}
|
||||||
|
for (var i = 0; i < this.target.dimensions.length; i++) {
|
||||||
|
if (!this.target.dimensions[i].key) {
|
||||||
|
this.target.error = "One or more dimensions is missing a key";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!this.target.dimensions[i].value){
|
||||||
|
this.target.error = "One or more dimensions is missing a value";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.target.error) {
|
||||||
|
console.log(this.target.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// METRIC
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.suggestMetrics = function(query, callback) {
|
||||||
|
if (!metricList) {
|
||||||
|
self.datasource.namesQuery()
|
||||||
|
.then(self.datasource.convertNamesList)
|
||||||
|
.then(function(metrics) {
|
||||||
|
metricList = metrics;
|
||||||
|
callback(metrics);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return metricList;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.onMetricChange = function() {
|
||||||
|
this.resetDimensionList();
|
||||||
|
this.targetBlur();
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// ALIAS
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.suggestAlias = function(query, callback) {
|
||||||
|
var upToLastTag = query.substr(0, query.lastIndexOf('@'));
|
||||||
|
var suggestions = self.datasource.listTemplates();
|
||||||
|
var dimensions = self.suggestDimensionKeys(query, callback);
|
||||||
|
for (var i = 0; i < dimensions.length; i++) {
|
||||||
|
suggestions.push(upToLastTag+"@"+dimensions[i]);
|
||||||
|
}
|
||||||
|
return suggestions;
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// DIMENSIONS
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.resetDimensionList = function() {
|
||||||
|
dimensionList = { 'keys' : [], 'values' : {} };
|
||||||
|
if (this.target.metric) {
|
||||||
|
this.datasource.metricsQuery({'name' : this.target.metric})
|
||||||
|
.then(this.datasource.buildDimensionList)
|
||||||
|
.then(function(dimensions) {
|
||||||
|
dimensionList = dimensions;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.suggestDimensionKeys = function(query, callback) {
|
||||||
|
if (dimensionList.keys.length === 0 && self.target.metric) {
|
||||||
|
self.datasource.metricsQuery({'name' : self.target.metric})
|
||||||
|
.then(self.datasource.buildDimensionList)
|
||||||
|
.then(function(dimensions) {
|
||||||
|
dimensionList = dimensions;
|
||||||
|
callback(dimensions.keys);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return dimensionList.keys;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.suggestDimensionValues = function(query, callback) {
|
||||||
|
var values = ['$all'];
|
||||||
|
values = values.concat(self.datasource.listTemplates());
|
||||||
|
if (currentDimension.key && currentDimension.key in dimensionList.values) {
|
||||||
|
values = values.concat(dimensionList.values[currentDimension.key]);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.editDimension = function(index) {
|
||||||
|
currentDimension = this.target.dimensions[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.addDimension = function() {
|
||||||
|
this.target.dimensions.push({});
|
||||||
|
};
|
||||||
|
|
||||||
|
MonascaQueryCtrl.prototype.removeDimension = function(index) {
|
||||||
|
this.target.dimensions.splice(index, 1);
|
||||||
|
this.targetBlur();
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
return MonascaQueryCtrl;
|
||||||
|
|
||||||
|
})(sdk.QueryCtrl);
|
||||||
|
|
||||||
|
return MonascaQueryCtrl;
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user