Skip to content

Commit 59b7f89

Browse files
committed
Add prometheus metrics to the application
1 parent dd52749 commit 59b7f89

18 files changed

+299
-162
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
FROM openjdk:11-jre
22
WORKDIR /channelfinder
3-
ADD https://repo1.maven.org/maven2/org/phoebus/app-channel-channelfinder/4.7.0/app-channel-channelfinder-4.7.0.jar .
3+
ADD https://repo1.maven.org/maven2/org/phoebus/app-channel-channelfinder/4.7.2/app-channel-channelfinder-4.7.2.jar .

docker-compose.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ services:
1818
echo 'Waiting for Elasticsearch'
1919
sleep 1
2020
done
21-
java -jar /channelfinder/ChannelFinder-4.7.0.jar"
21+
java -jar /channelfinder/ChannelFinder-4.7.2.jar"
2222
2323
elasticsearch:
2424
image: docker.elastic.co/elasticsearch/elasticsearch:8.2.0
@@ -34,7 +34,6 @@ services:
3434
xpack.security.enabled: "false"
3535
volumes:
3636
- channelfinder-es-data:/usr/share/elasticsearch/data
37-
3837
volumes:
3938
channelfinder-es-data:
4039
driver: local

pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,18 @@
198198
<groupId>io.netty</groupId> <!-- Need for running tests on mac -->
199199
<artifactId>netty-all</artifactId>
200200
</dependency>
201+
202+
<!-- Metrics -->
203+
<dependency>
204+
<groupId>org.springframework.boot</groupId>
205+
<artifactId>spring-boot-starter-actuator</artifactId>
206+
<version>${spring.boot-version}</version>
207+
</dependency>
208+
<dependency>
209+
<groupId>io.micrometer</groupId>
210+
<artifactId>micrometer-registry-prometheus</artifactId>
211+
<scope>runtime</scope>
212+
</dependency>
201213
</dependencies>
202214
<build>
203215
<!-- read properties from the pom file and add them to the application.properties -->

src/main/java/org/phoebus/channelfinder/Application.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@
3131
import org.springframework.context.annotation.Bean;
3232
import org.springframework.context.annotation.ComponentScan;
3333
import org.springframework.core.task.TaskExecutor;
34+
import org.springframework.scheduling.annotation.EnableScheduling;
3435
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
3536
import org.springframework.util.FileCopyUtils;
3637

3738
@EnableAutoConfiguration
3839
@ComponentScan(basePackages="org.phoebus.channelfinder")
40+
@EnableScheduling
3941
@SpringBootApplication
4042
public class Application implements ApplicationRunner {
4143

src/main/java/org/phoebus/channelfinder/ChannelRepository.java

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
package org.phoebus.channelfinder;
22

3-
import java.io.IOException;
4-
import java.text.MessageFormat;
5-
import java.util.Collections;
6-
import java.util.Comparator;
7-
import java.util.HashSet;
8-
import java.util.List;
9-
import java.util.Map;
10-
import java.util.Optional;
11-
import java.util.logging.Level;
12-
import java.util.logging.Logger;
13-
import java.util.stream.Collectors;
14-
import java.util.stream.StreamSupport;
15-
3+
import co.elastic.clients.elasticsearch.ElasticsearchClient;
164
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
175
import co.elastic.clients.elasticsearch._types.FieldSort;
186
import co.elastic.clients.elasticsearch._types.Refresh;
@@ -27,7 +15,6 @@
2715
import co.elastic.clients.elasticsearch.core.BulkResponse;
2816
import co.elastic.clients.elasticsearch.core.CountRequest;
2917
import co.elastic.clients.elasticsearch.core.CountResponse;
30-
import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest;
3118
import co.elastic.clients.elasticsearch.core.DeleteResponse;
3219
import co.elastic.clients.elasticsearch.core.ExistsRequest;
3320
import co.elastic.clients.elasticsearch.core.GetResponse;
@@ -37,27 +24,36 @@
3724
import co.elastic.clients.elasticsearch.core.SearchResponse;
3825
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
3926
import co.elastic.clients.elasticsearch.core.search.Hit;
40-
import co.elastic.clients.elasticsearch.core.search.TrackHits;
4127
import co.elastic.clients.json.JsonData;
4228
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
29+
import com.fasterxml.jackson.databind.ObjectMapper;
4330
import org.phoebus.channelfinder.entity.Channel;
4431
import org.phoebus.channelfinder.entity.Property;
4532
import org.phoebus.channelfinder.entity.SearchResult;
4633
import org.phoebus.channelfinder.entity.Tag;
4734
import org.springframework.beans.factory.annotation.Autowired;
4835
import org.springframework.beans.factory.annotation.Qualifier;
49-
import org.springframework.beans.factory.annotation.Value;
5036
import org.springframework.context.annotation.Configuration;
5137
import org.springframework.data.repository.CrudRepository;
5238
import org.springframework.http.HttpStatus;
5339
import org.springframework.stereotype.Repository;
40+
import org.springframework.util.LinkedMultiValueMap;
5441
import org.springframework.util.MultiValueMap;
55-
56-
import com.fasterxml.jackson.databind.ObjectMapper;
57-
58-
import co.elastic.clients.elasticsearch.ElasticsearchClient;
5942
import org.springframework.web.server.ResponseStatusException;
6043

44+
import java.io.IOException;
45+
import java.text.MessageFormat;
46+
import java.util.Collections;
47+
import java.util.Comparator;
48+
import java.util.HashSet;
49+
import java.util.List;
50+
import java.util.Map;
51+
import java.util.Optional;
52+
import java.util.logging.Level;
53+
import java.util.logging.Logger;
54+
import java.util.stream.Collectors;
55+
import java.util.stream.StreamSupport;
56+
6157
@Repository
6258
@Configuration
6359
public class ChannelRepository implements CrudRepository<Channel, String> {
@@ -343,8 +339,7 @@ public List<Channel> findAllById(Iterable<String> channelIds) {
343339

344340
@Override
345341
public long count() {
346-
// NOT USED
347-
return 0;
342+
return this.count(new LinkedMultiValueMap<>());
348343
}
349344

350345
/**
@@ -392,7 +387,7 @@ public void deleteAll(Iterable<? extends Channel> channels) {
392387
try {
393388
BulkResponse result = client.bulk(br.build());
394389
} catch (IOException e) {
395-
e.printStackTrace();
390+
logger.log(Level.WARNING, e.getMessage(), e);
396391
}
397392
}
398393

@@ -572,6 +567,30 @@ public long count(MultiValueMap<String, String> searchParameters) {
572567
}
573568

574569

570+
/**
571+
* Match count
572+
* @param propertyName channel search property name
573+
* @param propertyValue channel search property value
574+
* @return count of the number of matches to the provided query
575+
*/
576+
public long countByProperty(String propertyName, String propertyValue) {
577+
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
578+
params.add(propertyName, propertyValue == null? "*" : propertyValue);
579+
return this.count(params);
580+
}
581+
582+
/**
583+
* Match count
584+
* @param tagName channel search tag
585+
* @return count of the number of matches to the provided query
586+
*/
587+
public long countByTag(String tagName) {
588+
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
589+
params.add("~tag", tagName);
590+
return this.count(params);
591+
}
592+
593+
575594

576595
@Override
577596
public void deleteAllById(Iterable<? extends String> ids) {

src/main/java/org/phoebus/channelfinder/ElasticConfig.java

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -98,39 +98,36 @@ public int getES_QUERY_SIZE() {
9898
.addMixIn(Tag.class, Tag.OnlyTag.class)
9999
.addMixIn(Property.class, Property.OnlyProperty.class);
100100

101-
102-
@Bean({ "searchClient" })
103-
public ElasticsearchClient getSearchClient() {
104-
if (searchClient == null) {
101+
private static ElasticsearchClient createClient(ElasticsearchClient currentClient, ObjectMapper objectMapper,
102+
String host, int port, String createIndices, ElasticConfig config) {
103+
ElasticsearchClient client;
104+
if (currentClient == null) {
105105
// Create the low-level client
106106
RestClient httpClient = RestClient.builder(new HttpHost(host, port)).build();
107107

108108
// Create the Java API Client with the same low level client
109109
ElasticsearchTransport transport = new RestClientTransport(httpClient, new JacksonJsonpMapper(objectMapper));
110110

111-
searchClient = new ElasticsearchClient(transport);
111+
client = new ElasticsearchClient(transport);
112+
} else {
113+
client = currentClient;
112114
}
113115
esInitialized.set(!Boolean.parseBoolean(createIndices));
114116
if (esInitialized.compareAndSet(false, true)) {
115-
elasticIndexValidation(searchClient);
117+
config.elasticIndexValidation(client);
116118
}
119+
return client;
120+
121+
}
122+
@Bean({ "searchClient" })
123+
public ElasticsearchClient getSearchClient() {
124+
searchClient = createClient(searchClient, objectMapper, host, port, createIndices, this);
117125
return searchClient;
118126
}
119127

120128
@Bean({ "indexClient" })
121129
public ElasticsearchClient getIndexClient() {
122-
if (indexClient == null) {
123-
// Create the low-level client
124-
RestClient httpClient = RestClient.builder(new HttpHost(host, port)).build();
125-
126-
// Create the Java API Client with the same low level client
127-
ElasticsearchTransport transport = new RestClientTransport(httpClient, new JacksonJsonpMapper(objectMapper));
128-
indexClient = new ElasticsearchClient(transport);
129-
}
130-
esInitialized.set(!Boolean.parseBoolean(createIndices));
131-
if (esInitialized.compareAndSet(false, true)) {
132-
elasticIndexValidation(indexClient);
133-
}
130+
indexClient = createClient(indexClient, objectMapper, host, port, createIndices, this);
134131
return indexClient;
135132
}
136133

@@ -149,50 +146,30 @@ public void contextDestroyed(ServletContextEvent sce) {
149146
}
150147

151148
/**
152-
* Create the indices and templates if they don't exist
153-
* @param client
149+
* Create the olog indices and templates if they don't exist
150+
* @param client client connected to elasticsearch
154151
*/
155152
void elasticIndexValidation(ElasticsearchClient client) {
156-
// ChannelFinder Index
157-
try (InputStream is = ElasticConfig.class.getResourceAsStream("/channel_mapping.json")) {
158-
BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(ES_CHANNEL_INDEX)));
159-
if(!exits.value()) {
153+
validateIndex(client, ES_CHANNEL_INDEX, "/channel_mapping.json");
154+
validateIndex(client, ES_TAG_INDEX, "/tag_mapping.json");
155+
validateIndex(client, ES_PROPERTY_INDEX, "/properties_mapping.json");
156+
}
160157

161-
CreateIndexResponse result = client.indices().create(
162-
CreateIndexRequest.of(
163-
c -> c.index(ES_CHANNEL_INDEX).withJson(is)));
164-
logger.log(Level.INFO, () -> MessageFormat.format(TextUtil.CREATED_INDEX_ACKNOWLEDGED, ES_CHANNEL_INDEX, result.acknowledged()));
165-
}
166-
} catch (IOException e) {
167-
logger.log(Level.WARNING, MessageFormat.format(TextUtil.FAILED_TO_CREATE_INDEX, ES_CHANNEL_INDEX), e);
168-
}
158+
private void validateIndex(ElasticsearchClient client, String esIndex, String mapping) {
169159

170-
// ChannelFinder tag Index
171-
try (InputStream is = ElasticConfig.class.getResourceAsStream("/tag_mapping.json")) {
172-
BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(ES_TAG_INDEX)));
160+
// ChannelFinder Index
161+
try (InputStream is = ElasticConfig.class.getResourceAsStream(mapping)) {
162+
BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(esIndex)));
173163
if(!exits.value()) {
174164

175165
CreateIndexResponse result = client.indices().create(
176166
CreateIndexRequest.of(
177-
c -> c.index(ES_TAG_INDEX).withJson(is)));
178-
logger.log(Level.INFO, () -> MessageFormat.format(TextUtil.CREATED_INDEX_ACKNOWLEDGED, ES_TAG_INDEX, result.acknowledged()));
167+
c -> c.index(esIndex).withJson(is)));
168+
logger.log(Level.INFO, () -> MessageFormat.format(TextUtil.CREATED_INDEX_ACKNOWLEDGED, esIndex, result.acknowledged()));
179169
}
180-
} catch (IOException e) {
181-
logger.log(Level.WARNING, MessageFormat.format(TextUtil.FAILED_TO_CREATE_INDEX, ES_TAG_INDEX), e);
182-
}
183-
184-
// ChannelFinder property Index
185-
try (InputStream is = ElasticConfig.class.getResourceAsStream("/properties_mapping.json")) {
186-
BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(ES_PROPERTY_INDEX)));
187-
if(!exits.value()) {
188170

189-
CreateIndexResponse result = client.indices().create(
190-
CreateIndexRequest.of(
191-
c -> c.index(ES_PROPERTY_INDEX).withJson(is)));
192-
logger.log(Level.INFO, () -> MessageFormat.format(TextUtil.CREATED_INDEX_ACKNOWLEDGED, ES_PROPERTY_INDEX, result.acknowledged()));
193-
}
194171
} catch (IOException e) {
195-
logger.log(Level.WARNING, MessageFormat.format(TextUtil.FAILED_TO_CREATE_INDEX, ES_PROPERTY_INDEX), e);
172+
logger.log(Level.WARNING, MessageFormat.format(TextUtil.FAILED_TO_CREATE_INDEX, esIndex), e);
196173
}
197174
}
198175
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package org.phoebus.channelfinder;
2+
3+
import io.micrometer.core.instrument.Gauge;
4+
import io.micrometer.core.instrument.MeterRegistry;
5+
import io.micrometer.core.instrument.MultiGauge;
6+
import io.micrometer.core.instrument.Tags;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.context.annotation.PropertySource;
10+
import org.springframework.scheduling.annotation.Scheduled;
11+
import org.springframework.stereotype.Service;
12+
import org.springframework.util.LinkedMultiValueMap;
13+
14+
import java.util.Arrays;
15+
import java.util.Map;
16+
import java.util.logging.Level;
17+
import java.util.logging.Logger;
18+
import java.util.stream.Collectors;
19+
20+
21+
@Service
22+
@PropertySource(value = "classpath:application.properties")
23+
public class MetricsService {
24+
25+
@Value("${metrics.tags}")
26+
private String[] tags;
27+
28+
@Value("#{${metrics.properties:{'pvStatus': 'Active'}}}")
29+
private Map<String, String> properties;
30+
31+
private static final Logger logger = Logger.getLogger(MetricsService.class.getName());
32+
private static final String METRIC_NAME_CHANNEL_COUNT = "cf_channel_count";
33+
private static final String METRIC_NAME_PROPERTIES_COUNT = "cf_properties_count";
34+
private static final String METRIC_NAME_TAGS_COUNT = "cf_tags_count";
35+
private static final String METRIC_NAME_CHANNEL_COUNT_PER_PROPERTY = "cf_channel_count_per_property";
36+
private static final String METRIC_NAME_CHANNEL_COUNT_PER_TAG = "cf_channel_count_per_tag";
37+
private static final String METRIC_DESCRIPTION_CHANNEL_COUNT = "Count of channel finder channels";
38+
private static final String METRIC_DESCRIPTION_PROPERTIES_COUNT = "Count of channel finder properties";
39+
private static final String METRIC_DESCRIPTION_TAGS_COUNT = "Count of channel finder tags";
40+
private static final String METRIC_DESCRIPTION_CHANNEL_COUNT_PER_PROPERTY = "Count of channels with specific property with and specific value";
41+
private static final String METRIC_DESCRIPTION_CHANNEL_COUNT_PER_TAG = "Count of channels with specific tag";
42+
43+
private final ChannelRepository channelRepository;
44+
private final PropertyRepository propertyRepository;
45+
private final TagRepository tagRepository;
46+
private final MeterRegistry meterRegistry;
47+
48+
@Autowired
49+
public MetricsService(final ChannelRepository channelRepository, final PropertyRepository propertyRepository, final TagRepository tagRepository,
50+
final MeterRegistry meterRegistry) {
51+
this.channelRepository = channelRepository;
52+
this.propertyRepository = propertyRepository;
53+
this.tagRepository = tagRepository;
54+
this.meterRegistry = meterRegistry;
55+
registerGaugeMetrics();
56+
}
57+
58+
MultiGauge propertyCounts;
59+
MultiGauge tagCounts;
60+
61+
private void registerGaugeMetrics(){
62+
Gauge.builder(METRIC_NAME_CHANNEL_COUNT, () -> channelRepository.count(new LinkedMultiValueMap<>()))
63+
.description(METRIC_DESCRIPTION_CHANNEL_COUNT)
64+
.register(meterRegistry);
65+
Gauge.builder(METRIC_NAME_PROPERTIES_COUNT,
66+
propertyRepository::count)
67+
.description(METRIC_DESCRIPTION_PROPERTIES_COUNT)
68+
.register(meterRegistry);
69+
Gauge.builder(METRIC_NAME_TAGS_COUNT,
70+
tagRepository::count)
71+
.description(METRIC_DESCRIPTION_TAGS_COUNT)
72+
.register(meterRegistry);
73+
74+
propertyCounts = MultiGauge.builder(METRIC_NAME_CHANNEL_COUNT_PER_PROPERTY)
75+
.description(METRIC_DESCRIPTION_CHANNEL_COUNT_PER_PROPERTY)
76+
.register(meterRegistry);
77+
78+
tagCounts = MultiGauge.builder(METRIC_NAME_CHANNEL_COUNT_PER_TAG)
79+
.description(METRIC_DESCRIPTION_CHANNEL_COUNT_PER_TAG)
80+
.register(meterRegistry);
81+
82+
}
83+
84+
@Scheduled(fixedRate = 10000)
85+
public void updateMetrics() {
86+
logger.log(Level.INFO, "Updating metrics");
87+
propertyCounts.register(
88+
properties.entrySet().stream().map(property -> MultiGauge.Row.of(Tags.of(property.getKey(), property.getValue()),
89+
channelRepository.countByProperty(property.getKey(), property.getValue()))).collect(Collectors.toList())
90+
);
91+
tagCounts.register(
92+
Arrays.stream(tags).map(tag -> MultiGauge.Row.of(Tags.of("tag", tag),
93+
channelRepository.countByTag(tag))).collect(Collectors.toList())
94+
);
95+
}
96+
}

0 commit comments

Comments
 (0)