-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #590 from jenkinsci/add-grafana
Add Grafana integration
- Loading branch information
Showing
6 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
221 changes: 221 additions & 0 deletions
221
src/main/java/io/jenkins/plugins/opentelemetry/backend/GrafanaBackend.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
/* | ||
* Copyright The Original Author or Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.jenkins.plugins.opentelemetry.backend; | ||
|
||
import hudson.Extension; | ||
import hudson.util.FormValidation; | ||
import io.jenkins.plugins.opentelemetry.TemplateBindingsProvider; | ||
import org.apache.commons.lang.StringUtils; | ||
import org.jenkins.ui.icon.Icon; | ||
import org.jenkins.ui.icon.IconSet; | ||
import org.jenkinsci.Symbol; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.DataBoundSetter; | ||
import org.kohsuke.stapler.QueryParameter; | ||
|
||
import javax.annotation.Nullable; | ||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.util.HashMap; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
public class GrafanaBackend extends ObservabilityBackend implements TemplateBindingsProvider { | ||
|
||
public static final String DEFAULT_BACKEND_NAME = "Grafana"; | ||
|
||
public static final String OTEL_GRAFANA_URL = "OTEL_GRAFANA_URL"; | ||
|
||
private static final String DEFAULT_TEMPO_DATA_SOURCE_IDENTIFIER = "grafanacloud-traces"; | ||
private static final String DEFAULT_GRAFANA_ORG_ID = "1"; | ||
|
||
static { | ||
IconSet.icons.addIcon( | ||
new Icon( | ||
"icon-otel-grafana icon-sm", | ||
ICONS_PREFIX + "grafana.svg", | ||
Icon.ICON_SMALL_STYLE)); | ||
IconSet.icons.addIcon( | ||
new Icon( | ||
"icon-otel-grafana icon-md", | ||
ICONS_PREFIX + "grafana.svg", | ||
Icon.ICON_MEDIUM_STYLE)); | ||
IconSet.icons.addIcon( | ||
new Icon( | ||
"icon-otel-grafana icon-lg", | ||
ICONS_PREFIX + "grafana.svg", | ||
Icon.ICON_LARGE_STYLE)); | ||
IconSet.icons.addIcon( | ||
new Icon( | ||
"icon-otel-grafana icon-xlg", | ||
ICONS_PREFIX + "grafana.svg", | ||
Icon.ICON_XLARGE_STYLE)); | ||
} | ||
|
||
private String grafanaBaseUrl; | ||
|
||
private String grafanaMetricsDashboard; | ||
private String tempoDataSourceIdentifier = DEFAULT_TEMPO_DATA_SOURCE_IDENTIFIER; | ||
|
||
private String grafanaOrgId = DEFAULT_GRAFANA_ORG_ID; | ||
|
||
@DataBoundConstructor | ||
public GrafanaBackend() { | ||
|
||
} | ||
|
||
@Nullable | ||
@Override | ||
public String getTraceVisualisationUrlTemplate() { | ||
return | ||
"${" + TemplateBindings.GRAFANA_BASE_URL + "}" + | ||
"/explore?orgId=" + | ||
"${" + TemplateBindings.GRAFANA_ORG_ID + "}" + | ||
"&left=%7B%22datasource%22:%22" + | ||
"${" + TemplateBindings.GRAFANA_TEMPO_DATASOURCE_IDENTIFIER + "}" + | ||
"%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22datasource%22:%7B%22type%22:%22tempo%22,%22uid%22:%22" + | ||
"${" + TemplateBindings.GRAFANA_TEMPO_DATASOURCE_IDENTIFIER + "}" + | ||
"%22%7D,%22queryType%22:%22traceId%22,%22query%22:%22" + | ||
"${traceId}" + | ||
"%22%7D%5D,%22range%22:%7B%22from%22:%22" + | ||
"${startTime.minusSeconds(600).atZone(java.util.TimeZone.getDefault().toZoneId()).toInstant().toEpochMilli()}" + | ||
"%22,%22to%22:%22" + | ||
"${startTime.plusSeconds(600).atZone(java.util.TimeZone.getDefault().toZoneId()).toInstant().toEpochMilli()}" + | ||
"%22%7D%7D"; | ||
} | ||
|
||
/** | ||
* Not yet instrumented | ||
*/ | ||
@Nullable | ||
@Override | ||
public String getMetricsVisualizationUrlTemplate() { | ||
return grafanaMetricsDashboard; | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public String getIconPath() { | ||
return "icon-otel-grafana"; | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public String getEnvVariableName() { | ||
return OTEL_GRAFANA_URL; | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public String getDefaultName() { | ||
return DEFAULT_BACKEND_NAME; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
GrafanaBackend that = (GrafanaBackend) o; | ||
return grafanaOrgId == that.grafanaOrgId && Objects.equals(grafanaBaseUrl, that.grafanaBaseUrl) && Objects.equals(tempoDataSourceIdentifier, that.tempoDataSourceIdentifier); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(grafanaBaseUrl, tempoDataSourceIdentifier, grafanaOrgId); | ||
} | ||
|
||
@Override | ||
public Map<String, Object> mergeBindings(Map<String, Object> bindings) { | ||
Map<String, Object> mergedBindings = new HashMap<>(bindings); | ||
mergedBindings.putAll(getBindings()); | ||
return mergedBindings; | ||
} | ||
|
||
@Override | ||
public Map<String, String> getBindings() { | ||
Map<String, String> bindings = new LinkedHashMap<>(); | ||
bindings.put(TemplateBindings.BACKEND_NAME, getName()); | ||
bindings.put(ElasticBackend.TemplateBindings.BACKEND_24_24_ICON_URL, "/plugin/opentelemetry/images/24x24/grafana.png"); | ||
|
||
bindings.put(TemplateBindings.GRAFANA_BASE_URL, this.getGrafanaBaseUrl()); | ||
bindings.put(TemplateBindings.GRAFANA_ORG_ID, String.valueOf(this.getGrafanaOrgId())); | ||
bindings.put(TemplateBindings.GRAFANA_TEMPO_DATASOURCE_IDENTIFIER, this.getTempoDataSourceIdentifier()); | ||
|
||
return bindings; | ||
} | ||
|
||
public String getGrafanaBaseUrl() { | ||
return grafanaBaseUrl; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setGrafanaBaseUrl(String grafanaBaseUrl) { | ||
this.grafanaBaseUrl = grafanaBaseUrl; | ||
} | ||
|
||
@DataBoundSetter | ||
public String getTempoDataSourceIdentifier() { | ||
return tempoDataSourceIdentifier; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setTempoDataSourceIdentifier(String tempoDataSourceIdentifier) { | ||
this.tempoDataSourceIdentifier = tempoDataSourceIdentifier; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setGrafanaMetricsDashboard(String grafanaMetricsDashboard) { | ||
this.grafanaMetricsDashboard = grafanaMetricsDashboard; | ||
} | ||
|
||
public String getGrafanaOrgId() { | ||
return grafanaOrgId; | ||
} | ||
|
||
public void setGrafanaOrgId(String grafanaOrgId) { | ||
this.grafanaOrgId = grafanaOrgId; | ||
} | ||
|
||
@Extension | ||
@Symbol("grafana") | ||
public static class DescriptorImpl extends ObservabilityBackendDescriptor { | ||
|
||
@Override | ||
public String getDisplayName() { | ||
return DEFAULT_BACKEND_NAME; | ||
} | ||
|
||
public String getDefaultGrafanaOrgId() { | ||
return DEFAULT_GRAFANA_ORG_ID; | ||
} | ||
|
||
public String getDefaultTempoDataSourceIdentifier() { | ||
return DEFAULT_TEMPO_DATA_SOURCE_IDENTIFIER; | ||
} | ||
|
||
public FormValidation doCheckGrafanaBaseUrl(@QueryParameter("grafanaBaseUrl") String grafanaBaseUrl) { | ||
if (StringUtils.isEmpty(grafanaBaseUrl)) { | ||
return FormValidation.ok(); | ||
} | ||
try { | ||
new URL(grafanaBaseUrl); | ||
} catch (MalformedURLException e) { | ||
return FormValidation.error("Invalid URL: " + e.getMessage()); | ||
} | ||
return FormValidation.ok(); | ||
} | ||
} | ||
|
||
/** | ||
* List the attribute keys of the template bindings exposed by {@link ObservabilityBackend#getBindings()} | ||
*/ | ||
public interface TemplateBindings extends ObservabilityBackend.TemplateBindings { | ||
String GRAFANA_BASE_URL = "grafanaBaseUrl"; | ||
String GRAFANA_TEMPO_DATASOURCE_IDENTIFIER = "grafanaTempoDatasourceIdentifier"; | ||
String GRAFANA_ORG_ID = "grafanaOrgId"; | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
src/main/resources/io/jenkins/plugins/opentelemetry/backend/GrafanaBackend/config.jelly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?jelly escape-by-default='true'?> | ||
<j:jelly xmlns:j="jelly:core" | ||
xmlns:f="/lib/form" | ||
> | ||
<f:entry title="Grafana base URL" field="grafanaBaseUrl" description="e.g. 'https://example.grafana.net/'"> | ||
<f:textbox/> | ||
</f:entry> | ||
<f:entry title="Grafana metrics dashboard URL" field="grafanaMetricsDashboard" description="e.g. 'https://example.grafana.net/...'"> | ||
<f:textbox/> | ||
</f:entry> | ||
<f:advanced> | ||
<p> | ||
<strong>Grafana</strong> | ||
</p> | ||
<f:entry title="Tempo data source identifier" field="tempoDataSourceIdentifier" | ||
description="Identifier of the Tempo datasource in which the Jenkins pipeline build traces are stored."> | ||
<f:textbox default="${descriptor.defaultTempoDataSourceIdentifier}"/> | ||
</f:entry> | ||
<f:entry title="Grafana Org Id" field="grafanaOrgId"> | ||
<f:textbox default="${descriptor.defaultGrafanaOrgId}"/> | ||
</f:entry> | ||
<f:entry title="Display Name" field="name" description="Name used in Jenkins GUI"> | ||
<f:textbox default="${descriptor.displayName}"/> | ||
</f:entry> | ||
</f:advanced> | ||
</j:jelly> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions
36
src/test/java/io/jenkins/plugins/opentelemetry/backend/GrafanaBackendTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* Copyright The Original Author or Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.jenkins.plugins.opentelemetry.backend; | ||
|
||
import org.junit.Test; | ||
|
||
import java.time.LocalDateTime; | ||
import java.time.format.DateTimeFormatter; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class GrafanaBackendTest { | ||
|
||
@Test | ||
public void testTraceUrl() { | ||
GrafanaBackend grafanaBackend = new GrafanaBackend(); | ||
grafanaBackend.setGrafanaBaseUrl("https://cleclerc.grafana.net"); | ||
grafanaBackend.setGrafanaOrgId("1"); | ||
grafanaBackend.setTempoDataSourceIdentifier("grafanacloud-traces"); | ||
|
||
LocalDateTime buildTime = LocalDateTime.parse("2023-02-05 23:31:52.610", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")); | ||
|
||
Map<String, Object> bindings = new HashMap<>(); | ||
bindings.put("serviceName", "jenkins"); | ||
bindings.put("rootSpanName", "BUILD my-app"); | ||
bindings.put("traceId", "f464e1f32444443d3fc00fdb19e5c124"); | ||
bindings.put("spanId", "00799ea60984f33f"); | ||
bindings.put("startTime", buildTime); | ||
|
||
String actualTraceVisualisationUrl = grafanaBackend.getTraceVisualisationUrl(bindings); | ||
System.out.println(actualTraceVisualisationUrl); | ||
} | ||
} |