Skip to content

Commit 75eca84

Browse files
yaauieandsel
andauthored
metrics: add support for user-defined metrics (#18218)
* metrics: add support for user-defined metrics * Update logstash-core/src/main/java/org/logstash/instrument/metrics/UserMetric.java Co-authored-by: Andrea Selva <[email protected]> --------- Co-authored-by: Andrea Selva <[email protected]>
1 parent a152238 commit 75eca84

File tree

15 files changed

+246
-1
lines changed

15 files changed

+246
-1
lines changed

logstash-core/lib/logstash/instrument/collector.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ def get(namespaces_path, key, type)
7474
end
7575
end
7676

77+
##
78+
# @return [Metric]: the metric that exists after registration
79+
def register(namespaces_path, key, &metric_supplier)
80+
@metric_store.fetch_or_store(namespaces_path, key, &metric_supplier)
81+
end
82+
7783
# test injection, see MetricExtFactory
7884
def initialize_metric(type, namespaces_path, key)
7985
MetricType.create(type, namespaces_path, key)

logstash-core/src/main/java/co/elastic/logstash/api/NamespacedMetric.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ public interface NamespacedMetric extends Metric {
5151
*/
5252
TimerMetric timer(String metric);
5353

54+
/**
55+
* Creates or retrieves a {@link UserMetric} with the provided {@code metric} name,
56+
* using the supplied {@code userMetricFactory}.
57+
* @param metric the name of the metric
58+
* @param userMetricFactory a factory for creating the metric
59+
* @return the resulting metric at the address, whether retrieved or created
60+
* @param <USER_METRIC> the type of metric to create
61+
*/
62+
<USER_METRIC extends UserMetric<?>> USER_METRIC register(String metric, UserMetric.Factory<USER_METRIC> userMetricFactory);
63+
5464
/**
5565
* Increment the {@code metric} metric by 1.
5666
*
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package co.elastic.logstash.api;
2+
3+
import java.util.function.Function;
4+
5+
/**
6+
* A custom metric.
7+
* @param <VALUE_TYPE> must be jackson-serializable.
8+
*
9+
* NOTE: this interface is experimental and considered internal.
10+
* Its shape may change from one Logstash release to the next.
11+
*/
12+
public interface UserMetric<VALUE_TYPE> {
13+
VALUE_TYPE getValue();
14+
15+
/**
16+
* A {@link UserMetric.Factory<USER_METRIC>} is the primary way to register a custom user-metric
17+
* along-side a null implementation for performance when metrics are disabled.
18+
*
19+
* @param <USER_METRIC> a sub-interface of {@link UserMetric}
20+
*/
21+
interface Factory<USER_METRIC extends UserMetric<?>> {
22+
Class<USER_METRIC> getType();
23+
USER_METRIC create(String name);
24+
USER_METRIC nullImplementation();
25+
}
26+
27+
/**
28+
* A {@link UserMetric.Provider} is an intermediate helper type meant to be statically available by any
29+
* user-provided {@link UserMetric} interface, encapsulating its null implementation and providing
30+
* a way to simply get a {@link UserMetric.Factory} for a given non-null implementation.
31+
*
32+
* @param <USER_METRIC> an interface that extends {@link UserMetric}.
33+
*/
34+
class Provider<USER_METRIC extends UserMetric<?>> {
35+
private final Class<USER_METRIC> type;
36+
private final USER_METRIC nullImplementation;
37+
38+
public Provider(final Class<USER_METRIC> type, final USER_METRIC nullImplementation) {
39+
assert type.isInterface() : String.format("type must be an interface, got %s", type);
40+
41+
this.type = type;
42+
this.nullImplementation = nullImplementation;
43+
}
44+
45+
public Factory<USER_METRIC> getFactory(final Function<String, USER_METRIC> supplier) {
46+
return new Factory<USER_METRIC>() {
47+
@Override
48+
public USER_METRIC create(final String name) {
49+
return supplier.apply(name);
50+
}
51+
52+
@Override
53+
public Class<USER_METRIC> getType() {
54+
return type;
55+
}
56+
57+
@Override
58+
public USER_METRIC nullImplementation() {
59+
return nullImplementation;
60+
}
61+
};
62+
}
63+
}
64+
}

logstash-core/src/main/java/org/logstash/instrument/metrics/AbstractNamespacedMetricExt.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ public IRubyObject timer(final ThreadContext context, final IRubyObject key) {
5353
return getTimer(context, key);
5454
}
5555

56+
@JRubyMethod
57+
public IRubyObject register(final ThreadContext context, final IRubyObject key, final Block metricSupplier) {
58+
return doRegister(context, key, metricSupplier);
59+
}
60+
5661
@JRubyMethod(required = 1, optional = 1)
5762
public IRubyObject increment(final ThreadContext context, final IRubyObject[] args) {
5863
return doIncrement(context, args);
@@ -104,5 +109,7 @@ protected abstract IRubyObject doReportTime(ThreadContext context,
104109

105110
protected abstract IRubyObject doDecrement(ThreadContext context, IRubyObject[] args);
106111

112+
protected abstract IRubyObject doRegister(ThreadContext context, IRubyObject key, Block metricSupplier);
113+
107114
public abstract AbstractMetricExt getMetric();
108115
}

logstash-core/src/main/java/org/logstash/instrument/metrics/AbstractSimpleMetricExt.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ public IRubyObject time(final ThreadContext context,
7474
return doTime(context, namespace, key, block);
7575
}
7676

77+
@JRubyMethod(name = "register")
78+
public IRubyObject register(final ThreadContext context, final IRubyObject namespace, final IRubyObject key, final Block metricSupplier) {
79+
return doRegister(context, namespace, key, metricSupplier);
80+
}
81+
7782
protected abstract IRubyObject doDecrement(ThreadContext context, IRubyObject[] args);
7883

7984
protected abstract IRubyObject doIncrement(ThreadContext context, IRubyObject[] args);
@@ -88,4 +93,6 @@ protected abstract IRubyObject doReportTime(ThreadContext context, IRubyObject n
8893

8994
protected abstract IRubyObject doTime(ThreadContext context, IRubyObject namespace,
9095
IRubyObject key, Block block);
96+
97+
protected abstract IRubyObject doRegister(ThreadContext context, IRubyObject namespace, IRubyObject key, Block metricSupplier);
9198
}

logstash-core/src/main/java/org/logstash/instrument/metrics/MetricExt.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ protected IRubyObject getTimer(final ThreadContext context,
153153
);
154154
}
155155

156+
@Override
157+
protected IRubyObject doRegister(ThreadContext context, IRubyObject namespace, IRubyObject key, Block supplier) {
158+
MetricExt.validateKey(context, null, key);
159+
return collector.callMethod(context, "register", new IRubyObject[]{normalizeNamespace(namespace), key}, supplier);
160+
}
161+
156162
@Override
157163
protected IRubyObject doReportTime(final ThreadContext context, final IRubyObject namespace,
158164
final IRubyObject key, final IRubyObject duration) {

logstash-core/src/main/java/org/logstash/instrument/metrics/MetricType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ public enum MetricType {
7777
* A flow-rate {@link FlowMetric}, instantiated with one or more backing {@link Metric}{@code <Number>}.
7878
*/
7979
FLOW_RATE("flow/rate"),
80+
81+
/**
82+
* A user metric
83+
*/
84+
USER("user"),
8085
;
8186

8287
private final String type;

logstash-core/src/main/java/org/logstash/instrument/metrics/NamespacedMetricExt.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ public NamespacedMetricExt(final Ruby runtime, final RubyClass metaClass) {
5353
super(runtime, metaClass);
5454
}
5555

56+
@Override
57+
protected IRubyObject doRegister(ThreadContext context, IRubyObject key, Block metricSupplier) {
58+
return metric.register(context, namespaceName, key, metricSupplier);
59+
}
60+
5661
@JRubyMethod(visibility = Visibility.PRIVATE)
5762
public NamespacedMetricExt initialize(final ThreadContext context, final IRubyObject metric,
5863
final IRubyObject namespaceName) {

logstash-core/src/main/java/org/logstash/instrument/metrics/NullMetricExt.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ protected IRubyObject doTime(final ThreadContext context, final IRubyObject name
111111
return block.call(context);
112112
}
113113

114+
@Override
115+
protected IRubyObject doRegister(ThreadContext context, IRubyObject namespace, IRubyObject key, Block metricSupplier) {
116+
return context.nil;
117+
}
118+
114119
@Override
115120
protected AbstractNamespacedMetricExt createNamespaced(final ThreadContext context,
116121
final IRubyObject name) {

logstash-core/src/main/java/org/logstash/instrument/metrics/NullNamespacedMetricExt.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ protected IRubyObject doReportTime(final ThreadContext context, final IRubyObjec
112112
return context.nil;
113113
}
114114

115+
@Override
116+
protected IRubyObject doRegister(ThreadContext context, IRubyObject key, Block metricSupplier) {
117+
return context.nil;
118+
}
119+
115120
@Override
116121
@SuppressWarnings("rawtypes")
117122
protected RubyArray getNamespaceName(final ThreadContext context) {

0 commit comments

Comments
 (0)