Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Prometheus exporter module for Sentinel metrics #3173

Merged
merged 23 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sentinel-extension/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<module>sentinel-datasource-eureka</module>
<module>sentinel-annotation-cdi-interceptor</module>
<module>sentinel-metric-exporter</module>
<module>sentinel-prometheus-exporter</module>
LearningGp marked this conversation as resolved.
Show resolved Hide resolved
<module>sentinel-datasource-opensergo</module>
</modules>

Expand Down
79 changes: 79 additions & 0 deletions sentinel-extension/sentinel-prometheus-exporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Sentinel Prometheus Exporter

Sentinel Prometheus Exporter is a module which provides the Sentinel metrics data for prometheus.

You can integrate it into your Sentinel application, and then get the sentinel metrics in your prometheus.

## How it works
when the prometheus server collect the sentinel metrics,it get metrics from sentinel logs
![image](https://github.com/alibaba/Sentinel/assets/71377602/2982209b-a3c7-403b-ae50-1dc7a17f90b7)


## How to use
To use Sentinel Prometheus Exporter, you should add the following dependency:
### 1. add sentinel-prometheus-exporter
```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-prometheus-exporter</artifactId>
<version>x.y.z</version>
</dependency>
```

### 2. add prometheus dependency
```xml
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
<version>0.3.0</version>
</dependency>
```
```xml
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_httpserver</artifactId>
<version>0.3.0</version>
</dependency>
```


### 3. set prometheus.yml with fetch config

```yaml
scrape_configs:
- job_name: 'sentinelMetrics'
static_configs:
- targets: ['localhost:20001']
```

## Params for exporter
you can set system params to control the exporter behavior
### 1.sentinel.prometheus.port
the port for prometheus exporter,default 20001
### 2.sentinel.prometheus.size
the max fetch nums for prometheus exporter,in case the memory is not enough,default 1024
### 3.sentinel.prometheus.delay
the delay time for fetching , may be it is still do some statistics work according to the sliding window size when fetching,

so need to set the delay time to insure the accuracy.

unit: second

default: 0
### 4.sentinel.prometheus.identify
set the resource which need to fetch,default null,fetch all resources
### 5.sentinel.prometheus.types
the types need to fetch,such as passQps,concurrency

format:"xx|xx|xx"

default: "passQps|blockQps|exceptionQps|rt|concurrency"

you can reset the types as you need to,exm: "passQps|rt|concurrency|occupiedPassQps"

the type is same as the MetricNode class variables,with range:

{"passQps","blockQps","successQps","exceptionQps","rt","occupiedPassQps","concurrency"}

## how it looks
![image](https://github.com/alibaba/Sentinel/assets/71377602/dedde134-53ed-4b4e-b184-98e55184aacf)
42 changes: 42 additions & 0 deletions sentinel-extension/sentinel-prometheus-exporter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parent</artifactId>
<version>2.0.0-alpha</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>sentinel-prometheus-exporter</artifactId>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>

<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
<version>0.3.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_httpserver</artifactId>
<version>0.3.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.alibaba.csp.sentinel.prom;
LearningGp marked this conversation as resolved.
Show resolved Hide resolved

import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.prom.collector.SentinelCollector;
import io.prometheus.client.exporter.HTTPServer;

/**
* The{@link PromExporterInit} the InitFunc for prometheus exporter.
*
* @author karl-sy
* @date 2023-07-13 21:15
* @since 2.0.0
*/
public class PromExporterInit implements InitFunc {

private static final int promPort;

static {
promPort = Integer.parseInt(System.getProperty("sentinel.prometheus.port","20001"));
}

@Override
public void init() throws Exception {
HTTPServer server = null;
try {
new SentinelCollector().register();
// 开启http服务供prometheus调用
// 默认只提供一个接口 http://ip:port/metrics,返回所有指标
server = new HTTPServer(promPort);
} catch (Throwable e) {
RecordLog.warn("[PromExporterInit] failed to init prometheus exporter with exception:", e);
}

HTTPServer finalServer = server;
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (finalServer != null) {
finalServer.stop();
}
}));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.alibaba.csp.sentinel.prom.collector;
LearningGp marked this conversation as resolved.
Show resolved Hide resolved

import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.node.metric.MetricNode;
import com.alibaba.csp.sentinel.node.metric.MetricSearcher;
import com.alibaba.csp.sentinel.node.metric.MetricWriter;
import com.alibaba.csp.sentinel.prom.PromExporterInit;
import com.alibaba.csp.sentinel.util.PidUtil;
import io.prometheus.client.Collector;
import com.alibaba.csp.sentinel.prom.metrics.GaugeMetricFamily;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* The{@link PromExporterInit} the Collector for prometheus exporter.
*
* @author karl-sy
* @date 2023-07-13 21:15
* @since 2.0.0
*/
public class SentinelCollector extends Collector {

private final Object lock = new Object();
private volatile MetricSearcher searcher;

private volatile Long lastFetchTime;

private volatile String appName;

private volatile String[] types;

private volatile String identify;

private volatile int fetchSize;

private volatile int delayTime;

@Override
public List<MetricFamilySamples> collect() {
int ONE_SECOND = 1000;
if (searcher == null) {
synchronized (lock) {
fetchSize = Integer.parseInt(System.getProperty("sentinel.prometheus.size","1024"));
LearningGp marked this conversation as resolved.
Show resolved Hide resolved
delayTime = Integer.parseInt(System.getProperty("sentinel.prometheus.delay","0"));
identify = System.getProperty("sentinel.prometheus.identify",null);
String typeStr = System.getProperty("sentinel.prometheus.types","passQps|blockQps|exceptionQps|rt|concurrency");
types = typeStr.split("\\|");
appName = System.getProperty("sentinel.prometheus.app",SentinelConfig.getAppName());
if (appName == null) {
appName = "SENTINEL_APP";
}
if (searcher == null) {
searcher = new MetricSearcher(MetricWriter.METRIC_BASE_DIR,
MetricWriter.formMetricFileName(appName, PidUtil.getPid()));
}
appName = appName.replaceAll("\\.","_");
RecordLog.warn("[SentinelCollector] fetch sentinel metrics with appName:{}", appName);
lastFetchTime = System.currentTimeMillis() / ONE_SECOND * ONE_SECOND;
}
}

List<MetricFamilySamples> list = new ArrayList<>();

long endTime = System.currentTimeMillis() / ONE_SECOND * ONE_SECOND - (long) delayTime * ONE_SECOND;
try {
List<MetricNode> nodes = searcher.findByTimeAndResource(lastFetchTime, endTime, identify);
if(nodes == null){
lastFetchTime = endTime + ONE_SECOND;
return list;
}
if(nodes.size() > fetchSize){
nodes = nodes.subList(0,fetchSize);
}
GaugeMetricFamily exampleGaugeMetricFamily = new GaugeMetricFamily(appName,
"sentinel_metrics", Arrays.asList("resource","classification","type"));
for (MetricNode node : nodes) {
long recordTime = node.getTimestamp();
for (String type : types) {
double val = getTypeVal(node,type);
exampleGaugeMetricFamily.addMetric(Arrays.asList(node.getResource(), String.valueOf(node.getClassification()),type), val,recordTime);
}
}
list.add(exampleGaugeMetricFamily);
lastFetchTime = endTime + ONE_SECOND;
} catch (Exception e) {
RecordLog.warn("[SentinelCollector] failed to fetch sentinel metrics with exception:", e);
}

return list;
}

public double getTypeVal(MetricNode node,String type){
if("passQps".equals(type)){
return node.getPassQps();
}
if("blockQps".equals(type)){
return node.getBlockQps();
}
if("successQps".equals(type)){
return node.getSuccessQps();
}
if("exceptionQps".equals(type)){
return node.getExceptionQps();
}
if("rt".equals(type)){
return node.getRt();
}
if("occupiedPassQps".equals(type)){
return node.getOccupiedPassQps();
}
if("concurrency".equals(type)){
return node.getConcurrency();
}
return -1.0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.alibaba.csp.sentinel.prom.metrics;

import com.alibaba.csp.sentinel.prom.collector.SentinelCollector;
import io.prometheus.client.Collector;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* The{@link SentinelCollector} the MetricFamilySamples for prometheus exporter.
*
* @author karl-sy
* @date 2023-07-13 21:15
* @since 2.0.0
*/
public class GaugeMetricFamily extends Collector.MetricFamilySamples {

private final List<String> labelNames;

public GaugeMetricFamily(String name, String help, double value) {
super(name, Collector.Type.GAUGE, help, new ArrayList<Sample>());
labelNames = Collections.emptyList();
samples.add(new Sample(
name,
labelNames,
Collections.<String>emptyList(),
value));
}

public GaugeMetricFamily(String name, String help, List<String> labelNames) {
super(name, Collector.Type.GAUGE, help, new ArrayList<Sample>());
this.labelNames = labelNames;
}

public GaugeMetricFamily addMetric(List<String> labelValues, double value, long timestampMs) {
if (labelValues.size() != labelNames.size()) {
throw new IllegalArgumentException("Incorrect number of labels.");
}
samples.add(new Sample(name, labelNames, labelValues, value,timestampMs));
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.alibaba.csp.sentinel.prom.PromExporterInit
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.alibaba.csp.sentinel.prom.collector;

import com.alibaba.csp.sentinel.node.metric.MetricNode;
import com.alibaba.csp.sentinel.prom.metrics.GaugeMetricFamily;
import org.junit.Assert;
import org.junit.Test;

import java.util.Collections;

public class SentinelCollectorTest {
@Test
public void testCollector(){
SentinelCollector collector = new SentinelCollector();

MetricNode node = new MetricNode();
node.setPassQps(10);
double val = collector.getTypeVal(node,"passQps");
Assert.assertEquals(val, 10,1e-4);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.alibaba.csp.sentinel.prom.metrics;

import org.junit.Assert;
import org.junit.Test;

import java.util.Collections;

public class GaugeMetricFamilyTest {

@Test
public void testGaugeMetricFamily(){

GaugeMetricFamily metricFamily = new GaugeMetricFamily("appName",
"sentinel_metrics", Collections.singletonList("type"));
metricFamily.addMetric(Collections.singletonList("rt"), 1.0,System.currentTimeMillis());
Assert.assertEquals(metricFamily.samples.get(0).value, 1.0,1e-4);
}
}