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

APM Metering API #99832

Merged
merged 26 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
589b2e6
APM Metering API
stu-elastic Sep 22, 2023
1c55747
Update docs/changelog/99832.yaml
stu-elastic Sep 22, 2023
04189b4
depegate apm service creation, fix intstrument creation order in Abst…
stu-elastic Sep 25, 2023
47faa2e
3tests
pgomulka Sep 25, 2023
de9b4aa
APMMetric -> APMMeter
stu-elastic Sep 25, 2023
fd70837
Merge branch 'meter-noop-api' of github.com:stu-elastic/elasticsearch…
stu-elastic Sep 25, 2023
3a295fb
Metric -> Meter, MetricName -> String
stu-elastic Sep 25, 2023
f5ddd70
Add builder var in buildInstrument
stu-elastic Sep 25, 2023
d931b13
Create instrument with provider if enabled on construction
stu-elastic Sep 25, 2023
9eefc0b
javadocs, remove type param for unit, remove thread context version
stu-elastic Sep 25, 2023
4a3d95f
Merge branch 'test_stu-elastic-meter-noop-api' of github.com:pgomulka…
stu-elastic Sep 25, 2023
0d76486
pull Przemek's tests and refactor
stu-elastic Sep 25, 2023
3c40aa6
Remove Meter parameterization, remove @Test annotation, add javadocs
stu-elastic Sep 25, 2023
dbf00ab
tests, setup in apmmeter
pgomulka Sep 26, 2023
d063ca4
Merge remote-tracking branch 'upstream/main' into meter-noop-api
pgomulka Sep 26, 2023
2b43a35
disable apm by default
pgomulka Sep 26, 2023
b3850da
fix security manager exceptions
pgomulka Sep 27, 2023
8513bc6
Add apm.metrics.enabled setting
stu-elastic Sep 28, 2023
6ade418
Merge branch 'main' of github.com:elastic/elasticsearch into meter-no…
stu-elastic Sep 28, 2023
7ba6ac2
Merge branch 'main' of github.com:elastic/elasticsearch into meter-no…
stu-elastic Sep 28, 2023
5f130d4
apm.tracing.metrics.enabled -> telemetry.metrics.enabled
stu-elastic Sep 28, 2023
774a41d
use telemetry.metrics.enabled setting in APMMeterTests
stu-elastic Sep 28, 2023
ffaf4ef
Merge branch 'main' of github.com:elastic/elasticsearch into meter-no…
stu-elastic Sep 28, 2023
bbd11de
APM_METRICS_ENABLED_SETTING -> TELEMETRY_METRICS_ENABLED_SETTING
stu-elastic Sep 28, 2023
5df2546
use method ref in createOtelMeter
stu-elastic Sep 28, 2023
c9f7d29
remove getServices in APMTracer
stu-elastic Sep 28, 2023
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
5 changes: 5 additions & 0 deletions docs/changelog/99832.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 99832
summary: APM Metering API
area: Infra/Core
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.telemetry.apm.internal.APMAgentSettings;
import org.elasticsearch.telemetry.apm.internal.APMTelemetryProvider;
import org.elasticsearch.telemetry.apm.internal.metrics.APMMeter;
import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
Expand Down Expand Up @@ -97,7 +98,9 @@ public Collection<Object> createComponents(
apmAgentSettings.syncAgentSystemProperties(settings);
apmAgentSettings.addClusterSettingsListeners(clusterService, telemetryProvider.get());

return List.of(apmTracer);
final APMMeter apmMeter = telemetryProvider.get().getMeter();

return List.of(apmTracer, apmMeter);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.telemetry.apm.internal.metrics.APMMeter;
import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer;

import java.security.AccessController;
Expand All @@ -40,14 +41,21 @@ public class APMAgentSettings {
* Sensible defaults that Elasticsearch configures. This cannot be done via the APM agent
* config file, as then their values could not be overridden dynamically via system properties.
*/
static Map<String, String> APM_AGENT_DEFAULT_SETTINGS = Map.of("transaction_sample_rate", "0.2");
static Map<String, String> APM_AGENT_DEFAULT_SETTINGS = Map.of(
"transaction_sample_rate",
"0.2",
"enable_experimental_instrumentations",
"true"
);

public void addClusterSettingsListeners(ClusterService clusterService, APMTelemetryProvider apmTelemetryProvider) {
final ClusterSettings clusterSettings = clusterService.getClusterSettings();
final APMTracer apmTracer = apmTelemetryProvider.getTracer();
final APMMeter apmMeter = apmTelemetryProvider.getMeter();

clusterSettings.addSettingsUpdateConsumer(APM_ENABLED_SETTING, enabled -> {
apmTracer.setEnabled(enabled);
apmMeter.setEnabled(enabled);
// The agent records data other than spans, e.g. JVM metrics, so we toggle this setting in order to
// minimise its impact to a running Elasticsearch.
this.setAgentSetting("recording", Boolean.toString(enabled));
Expand Down Expand Up @@ -159,7 +167,7 @@ public void setAgentSetting(String key, String value) {

public static final Setting<Boolean> APM_ENABLED_SETTING = Setting.boolSetting(
APM_SETTING_PREFIX + "enabled",
false,
true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should switch this back to false before we merge and override this in serverless

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding a commit to do this.

OperatorDynamic,
NodeScope
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,27 @@

import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.telemetry.apm.internal.metrics.APMMeter;
import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer;

public class APMTelemetryProvider implements TelemetryProvider {
private final Settings settings;
private final APMTracer apmTracer;
private final APMMeter apmMeter;

public APMTelemetryProvider(Settings settings) {
this.settings = settings;
apmTracer = new APMTracer(settings);
apmMeter = new APMMeter(settings);
}

@Override
public APMTracer getTracer() {
return apmTracer;
}

@Override
public APMMeter getMeter() {
return apmMeter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.telemetry.apm.internal.metrics;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;

import org.apache.lucene.util.SetOnce;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.telemetry.metric.DoubleCounter;
import org.elasticsearch.telemetry.metric.DoubleGauge;
import org.elasticsearch.telemetry.metric.DoubleHistogram;
import org.elasticsearch.telemetry.metric.DoubleUpDownCounter;
import org.elasticsearch.telemetry.metric.LongCounter;
import org.elasticsearch.telemetry.metric.LongGauge;
import org.elasticsearch.telemetry.metric.LongHistogram;
import org.elasticsearch.telemetry.metric.LongUpDownCounter;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.function.Supplier;

import static org.elasticsearch.telemetry.apm.internal.APMAgentSettings.APM_ENABLED_SETTING;

public class APMMeter extends AbstractLifecycleComponent implements org.elasticsearch.telemetry.metric.Meter {
private final Instruments instruments;

private final Supplier<Meter> otelMeterSupplier;
private final Supplier<Meter> noopMeterSupplier;

private volatile boolean enabled;

public APMMeter(Settings settings) {
this(settings, APMMeter::otelMeter, APMMeter::noopMeter);
}

public APMMeter(Settings settings, Supplier<Meter> otelMeterSupplier, Supplier<Meter> noopMeterSupplier) {
this.enabled = APM_ENABLED_SETTING.get(settings);
this.otelMeterSupplier = otelMeterSupplier;
this.noopMeterSupplier = noopMeterSupplier;
this.instruments = new Instruments(enabled ? createApmServices() : noopMeterSupplier.get());
setEnabled(enabled);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the above line, we no longer have to call setEnabled

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding a commit to do this.

}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
setupApmServices(this.enabled);
}

private void setupApmServices(boolean enabled) {
if (enabled) {
instruments.setProvider(createApmServices());
} else {
destroyApmServices();
}
}

@Override
protected void doStart() {
if (enabled) {
createApmServices();
}
}

@Override
protected void doStop() {
destroyApmServices();
}

@Override
protected void doClose() {}

@Override
public DoubleCounter registerDoubleCounter(String name, String description, String unit) {
return instruments.registerDoubleCounter(name, description, unit);
}

@Override
public DoubleCounter getDoubleCounter(String name) {
return instruments.getDoubleCounter(name);
}

@Override
public DoubleUpDownCounter registerDoubleUpDownCounter(String name, String description, String unit) {
return instruments.registerDoubleUpDownCounter(name, description, unit);
}

@Override
public DoubleUpDownCounter getDoubleUpDownCounter(String name) {
return instruments.getDoubleUpDownCounter(name);
}

@Override
public DoubleGauge registerDoubleGauge(String name, String description, String unit) {
return instruments.registerDoubleGauge(name, description, unit);
}

@Override
public DoubleGauge getDoubleGauge(String name) {
return instruments.getDoubleGauge(name);
}

@Override
public DoubleHistogram registerDoubleHistogram(String name, String description, String unit) {
return instruments.registerDoubleHistogram(name, description, unit);
}

@Override
public DoubleHistogram getDoubleHistogram(String name) {
return instruments.getDoubleHistogram(name);
}

@Override
public LongCounter registerLongCounter(String name, String description, String unit) {
return instruments.registerLongCounter(name, description, unit);
}

@Override
public LongCounter getLongCounter(String name) {
return instruments.getLongCounter(name);
}

@Override
public LongUpDownCounter registerLongUpDownCounter(String name, String description, String unit) {
return instruments.registerLongUpDownCounter(name, description, unit);
}

@Override
public LongUpDownCounter getLongUpDownCounter(String name) {
return instruments.getLongUpDownCounter(name);
}

@Override
public LongGauge registerLongGauge(String name, String description, String unit) {
return instruments.registerLongGauge(name, description, unit);
}

@Override
public LongGauge getLongGauge(String name) {
return instruments.getLongGauge(name);
}

@Override
public LongHistogram registerLongHistogram(String name, String description, String unit) {
return instruments.registerLongHistogram(name, description, unit);
}

@Override
public LongHistogram getLongHistogram(String name) {
return instruments.getLongHistogram(name);
}

Meter createApmServices() {
assert this.enabled;

SetOnce<Meter> provider = new SetOnce<>();
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
provider.set(otelMeterSupplier.get());
return null;
});
return provider.get();
}

private void destroyApmServices() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could refactor this method to return noop instance.
(same as above createApmServices)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding a commit to do this.

instruments.setProvider(noopMeterSupplier.get());
}

private static Meter noopMeter() {
return OpenTelemetry.noop().getMeter("noop");
}

private static Meter otelMeter() {
var openTelemetry = GlobalOpenTelemetry.get();
var meter = openTelemetry.getMeter("elasticsearch");
return meter;
}

// scope for testing
Instruments getInstruments() {
return instruments;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.telemetry.apm.internal.metrics;

import io.opentelemetry.api.metrics.Meter;

import org.elasticsearch.core.Nullable;
import org.elasticsearch.telemetry.metric.Instrument;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
* An instrument that contains the name, description and unit. The delegate may be replaced when
* the provider is updated.
* Subclasses should implement the builder, which is used on initialization and provider updates.
* @param <T> delegated instrument
*/
public abstract class AbstractInstrument<T> implements Instrument {
private final AtomicReference<T> delegate;
private final String name;
private final String description;
private final String unit;

public AbstractInstrument(Meter meter, String name, String description, String unit) {
this.name = Objects.requireNonNull(name);
this.description = Objects.requireNonNull(description);
this.unit = Objects.requireNonNull(unit);
this.delegate = new AtomicReference<>(buildInstrument(meter));
}

@Override
public String getName() {
return name;
}

public String getUnit() {
return unit.toString();
}

T getInstrument() {
return delegate.get();
}

String getDescription() {
return description;
}

void setProvider(@Nullable Meter meter) {
delegate.set(buildInstrument(Objects.requireNonNull(meter)));
}

abstract T buildInstrument(Meter meter);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.telemetry.apm.internal.metrics;

import io.opentelemetry.api.metrics.Meter;

import java.util.Map;
import java.util.Objects;

/**
* DoubleGaugeAdapter wraps an otel ObservableDoubleMeasurement
*/
public class DoubleCounterAdapter extends AbstractInstrument<io.opentelemetry.api.metrics.DoubleCounter>
implements
org.elasticsearch.telemetry.metric.DoubleCounter {

public DoubleCounterAdapter(Meter meter, String name, String description, String unit) {
super(meter, name, description, unit);
}

io.opentelemetry.api.metrics.DoubleCounter buildInstrument(Meter meter) {
return Objects.requireNonNull(meter)
.counterBuilder(getName())
.ofDoubles()
.setDescription(getDescription())
.setUnit(getUnit())
.build();
}

@Override
public void increment() {
getInstrument().add(1d);
}

@Override
public void incrementBy(double inc) {
assert inc >= 0;
getInstrument().add(inc);
}

@Override
public void incrementBy(double inc, Map<String, Object> attributes) {
assert inc >= 0;
getInstrument().add(inc, OtelHelper.fromMap(attributes));
}
}
Loading