package org.stagemonitor.core.grafana; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.stagemonitor.core.CorePlugin; import org.stagemonitor.core.util.ExecutorUtils; import org.stagemonitor.core.util.HttpClient; import org.stagemonitor.util.IOUtils; import org.stagemonitor.core.util.JsonUtils; import org.stagemonitor.util.StringUtils; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; /** * Utility class for interacting with the Grafana HTTP API * <p/> * http://docs.grafana.org/reference/http_api/ */ public class GrafanaClient { private static final String ES_STAGEMONITOR_DS_NAME = "ES stagemonitor"; private final Logger logger = LoggerFactory.getLogger(getClass()); private final ThreadPoolExecutor asyncRestPool; private final CorePlugin corePlugin; private final HttpClient httpClient; public GrafanaClient(CorePlugin corePlugin) { this(corePlugin, new HttpClient()); } public GrafanaClient(CorePlugin corePlugin, HttpClient httpClient) { this.corePlugin = corePlugin; this.httpClient = httpClient; asyncRestPool = ExecutorUtils .createSingleThreadDeamonPool("async-grafana", corePlugin.getThreadPoolQueueCapacityLimit()); } public void createElasticsearchDatasource(final String url) { Map<String, Object> dataSource = new HashMap<String, Object>(); dataSource.put("name", ES_STAGEMONITOR_DS_NAME); dataSource.put("url", url); dataSource.put("access", "proxy"); dataSource.put("database", "[stagemonitor-metrics-]YYYY.MM.DD"); dataSource.put("isDefault", false); dataSource.put("type", "elasticsearch"); dataSource.put("basicAuth", false); Map<String, Object> jsonData = new HashMap<String, Object>(); jsonData.put("timeField", "@timestamp"); jsonData.put("interval", "Daily"); jsonData.put("timeInterval", ">" + corePlugin.getElasticsearchReportingInterval() + "s"); jsonData.put("esVersion", 5); dataSource.put("jsonData", jsonData); asyncGrafanaRequest("POST", "/api/datasources", dataSource); } /** * Saves a dashboard to Grafana * <p/> * If the Grafana url or the API Key is not configured, this method does nothing. * * @param classPathLocation The location of the dashboard */ public void sendGrafanaDashboardAsync(final String classPathLocation) { try { final ObjectNode dashboard = (ObjectNode) JsonUtils.getMapper().readTree(IOUtils.getResourceAsStream(classPathLocation)); dashboard.put("editable", false); addMinIntervalToPanels(dashboard, corePlugin.getElasticsearchReportingInterval() + "s"); final String requestBody = "{\"dashboard\":" + dashboard + ",\"overwrite\": true}"; asyncGrafanaRequest("POST", "/api/dashboards/db", requestBody); } catch (IOException e) { logger.warn(e.getMessage(), e); } } private void addMinIntervalToPanels(ObjectNode dashboard, String interval) { for (JsonNode row : dashboard.get("rows")) { for (JsonNode panel : row.get("panels")) { if (panel.has("datasource") && panel.get("datasource").asText().equals(ES_STAGEMONITOR_DS_NAME)) { ((ObjectNode) panel).put("interval", "$Interval"); } } } for (JsonNode template : dashboard.get("templating").get("list")) { if (template.has("name") && "Interval".equals(template.get("name").asText())) { ((ObjectNode) template).put("auto_min", interval); } } } private void asyncGrafanaRequest(final String method, final String path, final Object requestBody) { final String grafanaUrl = corePlugin.getGrafanaUrl(); final String grafanaApiToken = corePlugin.getGrafanaApiKey(); if (isGrafanaConfigured(grafanaUrl, grafanaApiToken)) { try { asyncRestPool.submit(new Runnable() { @Override public void run() { final Map<String, String> authHeader = Collections.singletonMap("Authorization", "Bearer " + grafanaApiToken); httpClient.sendAsJson(method, grafanaUrl + path, requestBody, authHeader); } }); } catch (RejectedExecutionException e) { ExecutorUtils.logRejectionWarning(e); } } else { logger.debug("Not requesting grafana, because the url or the api key is not configured."); } } private boolean isGrafanaConfigured(String grafanaUrl, String apiToken) { return StringUtils.isNotEmpty(grafanaUrl) && StringUtils.isNotEmpty(apiToken); } public void close() { asyncRestPool.shutdown(); } public void waitForCompletion() throws ExecutionException, InterruptedException { // because the pool is single threaded, // all previously submitted tasks are completed when this task finishes asyncRestPool.submit(new Runnable() { public void run() { } }).get(); } }