Add script compilation stats

This commit adds basic support to track the number of times scripts are
compiled and compiled scripts are evicted from the script cache. These
statistics are tracked at the node level.

Closes #12673
This commit is contained in:
Jason Tedor 2015-08-07 14:48:49 -04:00
parent 7efc419041
commit 23b348040e
15 changed files with 269 additions and 11 deletions

View File

@ -33,6 +33,7 @@ import org.elasticsearch.monitor.fs.FsInfo;
import org.elasticsearch.monitor.jvm.JvmStats;
import org.elasticsearch.monitor.os.OsStats;
import org.elasticsearch.monitor.process.ProcessStats;
import org.elasticsearch.script.ScriptStats;
import org.elasticsearch.threadpool.ThreadPoolStats;
import org.elasticsearch.transport.TransportStats;
@ -73,13 +74,17 @@ public class NodeStats extends BaseNodeResponse implements ToXContent {
@Nullable
private AllCircuitBreakerStats breaker;
@Nullable
private ScriptStats scriptStats;
NodeStats() {
}
public NodeStats(DiscoveryNode node, long timestamp, @Nullable NodeIndicesStats indices,
@Nullable OsStats os, @Nullable ProcessStats process, @Nullable JvmStats jvm, @Nullable ThreadPoolStats threadPool,
@Nullable FsInfo fs, @Nullable TransportStats transport, @Nullable HttpStats http,
@Nullable AllCircuitBreakerStats breaker) {
@Nullable AllCircuitBreakerStats breaker,
@Nullable ScriptStats scriptStats) {
super(node);
this.timestamp = timestamp;
this.indices = indices;
@ -91,6 +96,7 @@ public class NodeStats extends BaseNodeResponse implements ToXContent {
this.transport = transport;
this.http = http;
this.breaker = breaker;
this.scriptStats = scriptStats;
}
public long getTimestamp() {
@ -165,6 +171,11 @@ public class NodeStats extends BaseNodeResponse implements ToXContent {
return this.breaker;
}
@Nullable
public ScriptStats getScriptStats() {
return this.scriptStats;
}
public static NodeStats readNodeStats(StreamInput in) throws IOException {
NodeStats nodeInfo = new NodeStats();
nodeInfo.readFrom(in);
@ -200,6 +211,7 @@ public class NodeStats extends BaseNodeResponse implements ToXContent {
http = HttpStats.readHttpStats(in);
}
breaker = AllCircuitBreakerStats.readOptionalAllCircuitBreakerStats(in);
scriptStats = in.readOptionalStreamable(new ScriptStats());
}
@ -256,6 +268,7 @@ public class NodeStats extends BaseNodeResponse implements ToXContent {
http.writeTo(out);
}
out.writeOptionalStreamable(breaker);
out.writeOptionalStreamable(scriptStats);
}
@Override
@ -303,6 +316,9 @@ public class NodeStats extends BaseNodeResponse implements ToXContent {
if (getBreaker() != null) {
getBreaker().toXContent(builder, params);
}
if (getScriptStats() != null) {
getScriptStats().toXContent(builder, params);
}
return builder;
}

View File

@ -41,6 +41,7 @@ public class NodesStatsRequest extends BaseNodesRequest<NodesStatsRequest> {
private boolean transport;
private boolean http;
private boolean breaker;
private boolean script;
protected NodesStatsRequest() {
}
@ -67,6 +68,7 @@ public class NodesStatsRequest extends BaseNodesRequest<NodesStatsRequest> {
this.transport = true;
this.http = true;
this.breaker = true;
this.script = true;
return this;
}
@ -84,6 +86,7 @@ public class NodesStatsRequest extends BaseNodesRequest<NodesStatsRequest> {
this.transport = false;
this.http = false;
this.breaker = false;
this.script = false;
return this;
}
@ -240,6 +243,15 @@ public class NodesStatsRequest extends BaseNodesRequest<NodesStatsRequest> {
return this;
}
public boolean script() {
return script;
}
public NodesStatsRequest script(boolean script) {
this.script = script;
return this;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
@ -253,6 +265,7 @@ public class NodesStatsRequest extends BaseNodesRequest<NodesStatsRequest> {
transport = in.readBoolean();
http = in.readBoolean();
breaker = in.readBoolean();
script = in.readBoolean();
}
@Override
@ -268,6 +281,7 @@ public class NodesStatsRequest extends BaseNodesRequest<NodesStatsRequest> {
out.writeBoolean(transport);
out.writeBoolean(http);
out.writeBoolean(breaker);
out.writeBoolean(script);
}
}

View File

@ -62,6 +62,11 @@ public class NodesStatsRequestBuilder extends NodesOperationRequestBuilder<Nodes
return this;
}
public NodesStatsRequestBuilder setScript(boolean script) {
request.script(script);
return this;
}
/**
* Should the node indices stats be returned.
*/

View File

@ -80,7 +80,7 @@ public class TransportNodesStatsAction extends TransportNodesAction<NodesStatsRe
protected NodeStats nodeOperation(NodeStatsRequest nodeStatsRequest) {
NodesStatsRequest request = nodeStatsRequest.request;
return nodeService.stats(request.indices(), request.os(), request.process(), request.jvm(), request.threadPool(), request.network(),
request.fs(), request.transport(), request.http(), request.breaker());
request.fs(), request.transport(), request.http(), request.breaker(), request.script());
}
@Override

View File

@ -100,7 +100,7 @@ public class TransportClusterStatsAction extends TransportNodesAction<ClusterSta
@Override
protected ClusterStatsNodeResponse nodeOperation(ClusterStatsNodeRequest nodeRequest) {
NodeInfo nodeInfo = nodeService.info(false, true, false, true, false, false, true, false, true);
NodeStats nodeStats = nodeService.stats(CommonStatsFlags.NONE, false, true, true, false, false, true, false, false, false);
NodeStats nodeStats = nodeService.stats(CommonStatsFlags.NONE, false, true, true, false, false, true, false, false, false, false);
List<ShardStats> shardsStats = new ArrayList<>();
for (IndexService indexService : indicesService) {
for (IndexShard indexShard : indexService) {

View File

@ -36,6 +36,7 @@ import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.monitor.MonitorService;
import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
@ -51,6 +52,8 @@ public class NodeService extends AbstractComponent {
private final IndicesService indicesService;
private final PluginsService pluginService;
private final CircuitBreakerService circuitBreakerService;
private ScriptService scriptService;
@Nullable
private HttpServer httpServer;
@ -63,7 +66,8 @@ public class NodeService extends AbstractComponent {
@Inject
public NodeService(Settings settings, ThreadPool threadPool, MonitorService monitorService, Discovery discovery,
TransportService transportService, IndicesService indicesService,
PluginsService pluginService, CircuitBreakerService circuitBreakerService, Version version) {
PluginsService pluginService, CircuitBreakerService circuitBreakerService,
Version version) {
super(settings);
this.threadPool = threadPool;
this.monitorService = monitorService;
@ -76,6 +80,12 @@ public class NodeService extends AbstractComponent {
this.circuitBreakerService = circuitBreakerService;
}
// can not use constructor injection or there will be a circular dependency
@Inject(optional = true)
public void setScriptService(ScriptService scriptService) {
this.scriptService = scriptService;
}
public void setHttpServer(@Nullable HttpServer httpServer) {
this.httpServer = httpServer;
}
@ -134,12 +144,14 @@ public class NodeService extends AbstractComponent {
monitorService.fsService().stats(),
transportService.stats(),
httpServer == null ? null : httpServer.stats(),
circuitBreakerService.stats()
circuitBreakerService.stats(),
scriptService.stats()
);
}
public NodeStats stats(CommonStatsFlags indices, boolean os, boolean process, boolean jvm, boolean threadPool, boolean network,
boolean fs, boolean transport, boolean http, boolean circuitBreaker) {
boolean fs, boolean transport, boolean http, boolean circuitBreaker,
boolean script) {
// for indices stats we want to include previous allocated shards stats as well (it will
// only be applied to the sensible ones to use, like refresh/merge/flush/indexing stats)
return new NodeStats(discovery.localNode(), System.currentTimeMillis(),
@ -151,7 +163,8 @@ public class NodeService extends AbstractComponent {
fs ? monitorService.fsService().stats() : null,
transport ? transportService.stats() : null,
http ? (httpServer == null ? null : httpServer.stats()) : null,
circuitBreaker ? circuitBreakerService.stats() : null
circuitBreaker ? circuitBreakerService.stats() : null,
script ? scriptService.stats() : null
);
}
}

View File

@ -76,6 +76,7 @@ public class RestNodesStatsAction extends BaseRestHandler {
nodesStatsRequest.indices(metrics.contains("indices"));
nodesStatsRequest.process(metrics.contains("process"));
nodesStatsRequest.breaker(metrics.contains("breaker"));
nodesStatsRequest.script(metrics.contains("script"));
// check for index specific metrics
if (metrics.contains("indices")) {

View File

@ -57,6 +57,7 @@ import org.elasticsearch.rest.*;
import org.elasticsearch.rest.action.support.RestActionListener;
import org.elasticsearch.rest.action.support.RestResponseListener;
import org.elasticsearch.rest.action.support.RestTable;
import org.elasticsearch.script.ScriptStats;
import org.elasticsearch.search.suggest.completion.CompletionStats;
import java.util.Locale;
@ -92,7 +93,7 @@ public class RestNodesAction extends AbstractCatAction {
@Override
public void processResponse(final NodesInfoResponse nodesInfoResponse) {
NodesStatsRequest nodesStatsRequest = new NodesStatsRequest();
nodesStatsRequest.clear().jvm(true).os(true).fs(true).indices(true).process(true);
nodesStatsRequest.clear().jvm(true).os(true).fs(true).indices(true).process(true).script(true);
client.admin().cluster().nodesStats(nodesStatsRequest, new RestResponseListener<NodesStatsResponse>(channel) {
@Override
public RestResponse buildResponse(NodesStatsResponse nodesStatsResponse) throws Exception {
@ -183,6 +184,9 @@ public class RestNodesAction extends AbstractCatAction {
table.addCell("refresh.total", "alias:rto,refreshTotal;default:false;text-align:right;desc:total refreshes");
table.addCell("refresh.time", "alias:rti,refreshTime;default:false;text-align:right;desc:time spent in refreshes");
table.addCell("script.compilations", "alias:scrcc,scriptCompilations;default:false;text-align:right;desc:script compilations");
table.addCell("script.cache_evictions", "alias:scrce,scriptCacheEvictions;default:false;text-align:right;desc:script cache evictions");
table.addCell("search.fetch_current", "alias:sfc,searchFetchCurrent;default:false;text-align:right;desc:current fetch phase ops");
table.addCell("search.fetch_time", "alias:sfti,searchFetchTime;default:false;text-align:right;desc:time spent in fetch phase");
table.addCell("search.fetch_total", "alias:sfto,searchFetchTotal;default:false;text-align:right;desc:total fetch ops");
@ -317,6 +321,10 @@ public class RestNodesAction extends AbstractCatAction {
table.addCell(refreshStats == null ? null : refreshStats.getTotal());
table.addCell(refreshStats == null ? null : refreshStats.getTotalTime());
ScriptStats scriptStats = stats == null ? null : stats.getScriptStats();
table.addCell(scriptStats == null ? null : scriptStats.getCompilations());
table.addCell(scriptStats == null ? null : scriptStats.getCacheEvictions());
SearchStats searchStats = indicesStats == null ? null : indicesStats.getSearch();
table.addCell(searchStats == null ? null : searchStats.getTotal().getFetchCurrent());
table.addCell(searchStats == null ? null : searchStats.getTotal().getFetchTime());

View File

@ -0,0 +1,39 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script;
import org.elasticsearch.common.metrics.CounterMetric;
public class ScriptMetrics {
final CounterMetric compilationsMetric = new CounterMetric();
final CounterMetric cacheEvictionsMetric = new CounterMetric();
public ScriptStats stats() {
return new ScriptStats(compilationsMetric.count(), cacheEvictionsMetric.count());
}
public void onCompilation() {
compilationsMetric.inc();
}
public void onCacheEviction() {
cacheEvictionsMetric.inc();
}
}

View File

@ -84,6 +84,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
public static final String DEFAULT_SCRIPTING_LANGUAGE_SETTING = "script.default_lang";
public static final String SCRIPT_CACHE_SIZE_SETTING = "script.cache.max_size";
public static final int SCRIPT_CACHE_SIZE_DEFAULT = 100;
public static final String SCRIPT_CACHE_EXPIRE_SETTING = "script.cache.expire";
public static final String SCRIPT_INDEX = ".scripts";
public static final String DEFAULT_LANG = GroovyScriptEngineService.NAME;
@ -107,6 +108,8 @@ public class ScriptService extends AbstractComponent implements Closeable {
private Client client = null;
private final ScriptMetrics scriptMetrics = new ScriptMetrics();
/**
* @deprecated Use {@link org.elasticsearch.script.Script.ScriptField} instead. This should be removed in
* 2.0
@ -140,7 +143,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
this.scriptEngines = scriptEngines;
this.scriptContextRegistry = scriptContextRegistry;
int cacheMaxSize = settings.getAsInt(SCRIPT_CACHE_SIZE_SETTING, 100);
int cacheMaxSize = settings.getAsInt(SCRIPT_CACHE_SIZE_SETTING, SCRIPT_CACHE_SIZE_DEFAULT);
TimeValue cacheExpire = settings.getAsTime(SCRIPT_CACHE_EXPIRE_SETTING, null);
logger.debug("using script cache with max_size [{}], expire [{}]", cacheMaxSize, cacheExpire);
@ -306,6 +309,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
//Since the cache key is the script content itself we don't need to
//invalidate/check the cache if an indexed script changes.
scriptMetrics.onCompilation();
cache.put(cacheKey, compiledScript);
}
@ -474,6 +478,10 @@ public class ScriptService extends AbstractComponent implements Closeable {
}
}
public ScriptStats stats() {
return scriptMetrics.stats();
}
/**
* A small listener for the script cache that calls each
* {@code ScriptEngineService}'s {@code scriptRemoved} method when the
@ -486,6 +494,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
if (logger.isDebugEnabled()) {
logger.debug("notifying script services of script removal due to: [{}]", notification.getCause());
}
scriptMetrics.onCacheEviction();
for (ScriptEngineService service : scriptEngines) {
try {
service.scriptRemoved(notification.getValue());
@ -532,6 +541,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
String script = Streams.copyToString(reader);
String cacheKey = getCacheKey(engineService, scriptNameExt.v1(), null);
staticCache.put(cacheKey, new CompiledScript(ScriptType.FILE, scriptNameExt.v1(), engineService.types()[0], engineService.compile(script)));
scriptMetrics.onCompilation();
}
} else {
logger.warn("skipping compile of script file [{}] as all scripted operations are disabled for file scripts", file.toAbsolutePath());

View File

@ -0,0 +1,82 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentBuilderString;
import java.io.IOException;
public class ScriptStats implements Streamable, ToXContent {
private long compilations;
private long cacheEvictions;
public ScriptStats() {
}
public ScriptStats(long compilations, long cacheEvictions) {
this.compilations = compilations;
this.cacheEvictions = cacheEvictions;
}
public void add(ScriptStats stats) {
this.compilations += stats.compilations;
this.cacheEvictions += stats.cacheEvictions;
}
public long getCompilations() {
return compilations;
}
public long getCacheEvictions() {
return cacheEvictions;
}
@Override
public void readFrom(StreamInput in) throws IOException {
compilations = in.readVLong();
cacheEvictions = in.readVLong();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVLong(compilations);
out.writeVLong(cacheEvictions);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(Fields.SCRIPT_STATS);
builder.field(Fields.COMPILATIONS, getCompilations());
builder.field(Fields.CACHE_EVICTIONS, getCacheEvictions());
builder.endObject();
return builder;
}
static final class Fields {
static final XContentBuilderString SCRIPT_STATS = new XContentBuilderString("script");
static final XContentBuilderString COMPILATIONS = new XContentBuilderString("compilations");
static final XContentBuilderString CACHE_EVICTIONS = new XContentBuilderString("cache_evictions");
}
}

View File

@ -179,7 +179,8 @@ public class MockDiskUsagesIT extends ESIntegTestCase {
System.currentTimeMillis(),
null, null, null, null, null,
fsInfo,
null, null, null);
null, null, null,
null);
}
/**

View File

@ -387,6 +387,73 @@ public class ScriptServiceTests extends ESTestCase {
}
}
@Test
public void testCompileCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
scriptService.compile(new Script("1+1", ScriptType.INLINE, "test", null), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
@Test
public void testExecutableCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
scriptService.executable(new Script("1+1", ScriptType.INLINE, "test", null), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
@Test
public void testSearchCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
scriptService.search(null, new Script("1+1", ScriptType.INLINE, "test", null), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
@Test
public void testMultipleCompilationsCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
int numberOfCompilations = randomIntBetween(1, 1024);
for (int i = 0; i < numberOfCompilations; i++) {
scriptService.compile(new Script(String.format("%d+%d", i, i), ScriptType.INLINE, "test", null), randomFrom(scriptContexts));
}
assertEquals(numberOfCompilations, scriptService.stats().getCompilations());
}
@Test
public void testCompilationStatsOnCacheHit() throws IOException {
Settings.Builder builder = Settings.builder();
builder.put(ScriptService.SCRIPT_CACHE_SIZE_SETTING, 1);
buildScriptService(builder.build());
scriptService.executable(new Script("1+1", ScriptType.INLINE, "test", null), randomFrom(scriptContexts));
scriptService.executable(new Script("1+1", ScriptType.INLINE, "test", null), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
@Test
public void testFileScriptCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
createFileScripts("test");
scriptService.compile(new Script("file_script", ScriptType.FILE, "test", null), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
@Test
public void testIndexedScriptCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
scriptService.compile(new Script("script", ScriptType.INDEXED, "test", null), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
@Test
public void testCacheEvictionCountedInCacheEvictionsStats() throws IOException {
Settings.Builder builder = Settings.builder();
builder.put(ScriptService.SCRIPT_CACHE_SIZE_SETTING, 1);
buildScriptService(builder.build());
scriptService.executable(new Script("1+1", ScriptType.INLINE, "test", null), randomFrom(scriptContexts));
scriptService.executable(new Script("2+2", ScriptType.INLINE, "test", null), randomFrom(scriptContexts));
assertEquals(2L, scriptService.stats().getCompilations());
assertEquals(1L, scriptService.stats().getCacheEvictions());
}
private void createFileScripts(String... langs) throws IOException {
for (String lang : langs) {
Path scriptPath = scriptsFilePath.resolve("file_script." + lang);

View File

@ -1855,7 +1855,7 @@ public final class InternalTestCluster extends TestCluster {
}
NodeService nodeService = getInstanceFromNode(NodeService.class, nodeAndClient.node);
NodeStats stats = nodeService.stats(CommonStatsFlags.ALL, false, false, false, false, false, false, false, false, false);
NodeStats stats = nodeService.stats(CommonStatsFlags.ALL, false, false, false, false, false, false, false, false, false, false);
assertThat("Fielddata size must be 0 on node: " + stats.getNode(), stats.getIndices().getFieldData().getMemorySizeInBytes(), equalTo(0l));
assertThat("Query cache size must be 0 on node: " + stats.getNode(), stats.getIndices().getQueryCache().getMemorySizeInBytes(), equalTo(0l));
assertThat("FixedBitSet cache size must be 0 on node: " + stats.getNode(), stats.getIndices().getSegments().getBitsetMemoryInBytes(), equalTo(0l));

View File

@ -168,6 +168,8 @@ percolating |0s
|`percolate.total` |`pto`, `percolateTotal` |No |Total percolations |0
|`refresh.total` |`rto`, `refreshTotal` |No |Number of refreshes |16
|`refresh.time` |`rti`, `refreshTime` |No |Time spent in refreshes |91ms
|`script.compilations` |`scrcc`, `scriptCompilations` |No |Total script compilations |17
|`script.cache_evictions` |`scrce`, `scriptCacheEvictions` |No |Total compiled scripts evicted from cache |6
|`search.fetch_current` |`sfc`, `searchFetchCurrent` |No |Current fetch
phase operations |0
|`search.fetch_time` |`sfti`, `searchFetchTime` |No |Time spent in fetch