Skip to content

Commit

Permalink
Report timing stats as part of the Job stats response (#42709)
Browse files Browse the repository at this point in the history
  • Loading branch information
przemekwitek committed Jun 13, 2019
1 parent eaf76d2 commit 38f3a74
Show file tree
Hide file tree
Showing 29 changed files with 1,052 additions and 102 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* 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.client.ml.job.process;

import org.elasticsearch.client.ml.job.config.Job;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Objects;

import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;

/**
* Stats that give more insight into timing of various operations performed as part of anomaly detection job.
*/
public class TimingStats implements ToXContentObject {

public static final ParseField BUCKET_COUNT = new ParseField("bucket_count");
public static final ParseField MIN_BUCKET_PROCESSING_TIME_MS = new ParseField("minimum_bucket_processing_time_ms");
public static final ParseField MAX_BUCKET_PROCESSING_TIME_MS = new ParseField("maximum_bucket_processing_time_ms");
public static final ParseField AVG_BUCKET_PROCESSING_TIME_MS = new ParseField("average_bucket_processing_time_ms");

public static final ConstructingObjectParser<TimingStats, Void> PARSER =
new ConstructingObjectParser<>(
"timing_stats",
true,
args -> new TimingStats((String) args[0], (long) args[1], (Double) args[2], (Double) args[3], (Double) args[4]));

static {
PARSER.declareString(constructorArg(), Job.ID);
PARSER.declareLong(constructorArg(), BUCKET_COUNT);
PARSER.declareDouble(optionalConstructorArg(), MIN_BUCKET_PROCESSING_TIME_MS);
PARSER.declareDouble(optionalConstructorArg(), MAX_BUCKET_PROCESSING_TIME_MS);
PARSER.declareDouble(optionalConstructorArg(), AVG_BUCKET_PROCESSING_TIME_MS);
}

private final String jobId;
private long bucketCount;
private Double minBucketProcessingTimeMs;
private Double maxBucketProcessingTimeMs;
private Double avgBucketProcessingTimeMs;

public TimingStats(
String jobId,
long bucketCount,
@Nullable Double minBucketProcessingTimeMs,
@Nullable Double maxBucketProcessingTimeMs,
@Nullable Double avgBucketProcessingTimeMs) {
this.jobId = jobId;
this.bucketCount = bucketCount;
this.minBucketProcessingTimeMs = minBucketProcessingTimeMs;
this.maxBucketProcessingTimeMs = maxBucketProcessingTimeMs;
this.avgBucketProcessingTimeMs = avgBucketProcessingTimeMs;
}

public String getJobId() {
return jobId;
}

public long getBucketCount() {
return bucketCount;
}

public Double getMinBucketProcessingTimeMs() {
return minBucketProcessingTimeMs;
}

public Double getMaxBucketProcessingTimeMs() {
return maxBucketProcessingTimeMs;
}

public Double getAvgBucketProcessingTimeMs() {
return avgBucketProcessingTimeMs;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(Job.ID.getPreferredName(), jobId);
builder.field(BUCKET_COUNT.getPreferredName(), bucketCount);
if (minBucketProcessingTimeMs != null) {
builder.field(MIN_BUCKET_PROCESSING_TIME_MS.getPreferredName(), minBucketProcessingTimeMs);
}
if (maxBucketProcessingTimeMs != null) {
builder.field(MAX_BUCKET_PROCESSING_TIME_MS.getPreferredName(), maxBucketProcessingTimeMs);
}
if (avgBucketProcessingTimeMs != null) {
builder.field(AVG_BUCKET_PROCESSING_TIME_MS.getPreferredName(), avgBucketProcessingTimeMs);
}
builder.endObject();
return builder;
}

@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null || getClass() != o.getClass()) return false;
TimingStats that = (TimingStats) o;
return Objects.equals(this.jobId, that.jobId)
&& this.bucketCount == that.bucketCount
&& Objects.equals(this.minBucketProcessingTimeMs, that.minBucketProcessingTimeMs)
&& Objects.equals(this.maxBucketProcessingTimeMs, that.maxBucketProcessingTimeMs)
&& Objects.equals(this.avgBucketProcessingTimeMs, that.avgBucketProcessingTimeMs);
}

@Override
public int hashCode() {
return Objects.hash(jobId, bucketCount, minBucketProcessingTimeMs, maxBucketProcessingTimeMs, avgBucketProcessingTimeMs);
}

@Override
public String toString() {
return Strings.toString(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.elasticsearch.client.ml.job.config.JobState;
import org.elasticsearch.client.ml.job.process.DataCounts;
import org.elasticsearch.client.ml.job.process.ModelSizeStats;
import org.elasticsearch.client.ml.job.process.TimingStats;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.unit.TimeValue;
Expand All @@ -42,6 +43,7 @@ public class JobStats implements ToXContentObject {

private static final ParseField DATA_COUNTS = new ParseField("data_counts");
private static final ParseField MODEL_SIZE_STATS = new ParseField("model_size_stats");
private static final ParseField TIMING_STATS = new ParseField("timing_stats");
private static final ParseField FORECASTS_STATS = new ParseField("forecasts_stats");
private static final ParseField STATE = new ParseField("state");
private static final ParseField NODE = new ParseField("node");
Expand All @@ -58,6 +60,7 @@ public class JobStats implements ToXContentObject {
JobState jobState = (JobState) a[i++];
ModelSizeStats.Builder modelSizeStatsBuilder = (ModelSizeStats.Builder) a[i++];
ModelSizeStats modelSizeStats = modelSizeStatsBuilder == null ? null : modelSizeStatsBuilder.build();
TimingStats timingStats = (TimingStats) a[i++];
ForecastStats forecastStats = (ForecastStats) a[i++];
NodeAttributes node = (NodeAttributes) a[i++];
String assignmentExplanation = (String) a[i++];
Expand All @@ -66,6 +69,7 @@ public class JobStats implements ToXContentObject {
dataCounts,
jobState,
modelSizeStats,
timingStats,
forecastStats,
node,
assignmentExplanation,
Expand All @@ -80,6 +84,7 @@ public class JobStats implements ToXContentObject {
STATE,
ObjectParser.ValueType.VALUE);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), ModelSizeStats.PARSER, MODEL_SIZE_STATS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), TimingStats.PARSER, TIMING_STATS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), ForecastStats.PARSER, FORECASTS_STATS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), NodeAttributes.PARSER, NODE);
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), ASSIGNMENT_EXPLANATION);
Expand All @@ -94,22 +99,24 @@ public class JobStats implements ToXContentObject {
private final DataCounts dataCounts;
private final JobState state;
private final ModelSizeStats modelSizeStats;
private final TimingStats timingStats;
private final ForecastStats forecastStats;
private final NodeAttributes node;
private final String assignmentExplanation;
private final TimeValue openTime;

JobStats(String jobId, DataCounts dataCounts, JobState state, @Nullable ModelSizeStats modelSizeStats,
@Nullable ForecastStats forecastStats, @Nullable NodeAttributes node,
@Nullable String assignmentExplanation, @Nullable TimeValue opentime) {
@Nullable TimingStats timingStats, @Nullable ForecastStats forecastStats, @Nullable NodeAttributes node,
@Nullable String assignmentExplanation, @Nullable TimeValue openTime) {
this.jobId = Objects.requireNonNull(jobId);
this.dataCounts = Objects.requireNonNull(dataCounts);
this.state = Objects.requireNonNull(state);
this.modelSizeStats = modelSizeStats;
this.timingStats = timingStats;
this.forecastStats = forecastStats;
this.node = node;
this.assignmentExplanation = assignmentExplanation;
this.openTime = opentime;
this.openTime = openTime;
}

/**
Expand All @@ -135,6 +142,10 @@ public ModelSizeStats getModelSizeStats() {
return modelSizeStats;
}

public TimingStats getTimingStats() {
return timingStats;
}

/**
* An object that provides statistical information about forecasts of this job.
* See {@link ForecastStats}
Expand Down Expand Up @@ -182,6 +193,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
if (modelSizeStats != null) {
builder.field(MODEL_SIZE_STATS.getPreferredName(), modelSizeStats);
}
if (timingStats != null) {
builder.field(TIMING_STATS.getPreferredName(), timingStats);
}
if (forecastStats != null) {
builder.field(FORECASTS_STATS.getPreferredName(), forecastStats);
}
Expand All @@ -199,7 +213,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws

@Override
public int hashCode() {
return Objects.hash(jobId, dataCounts, modelSizeStats, forecastStats, state, node, assignmentExplanation, openTime);
return Objects.hash(jobId, dataCounts, modelSizeStats, timingStats, forecastStats, state, node, assignmentExplanation, openTime);
}

@Override
Expand All @@ -216,6 +230,7 @@ public boolean equals(Object obj) {
return Objects.equals(jobId, other.jobId) &&
Objects.equals(this.dataCounts, other.dataCounts) &&
Objects.equals(this.modelSizeStats, other.modelSizeStats) &&
Objects.equals(this.timingStats, other.timingStats) &&
Objects.equals(this.forecastStats, other.forecastStats) &&
Objects.equals(this.state, other.state) &&
Objects.equals(this.node, other.node) &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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.client.ml.job.process;

import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;

import static org.hamcrest.Matchers.equalTo;

public class TimingStatsTests extends AbstractXContentTestCase<TimingStats> {

private static final String JOB_ID = "my-job-id";

public static TimingStats createTestInstance(String jobId) {
return new TimingStats(
jobId,
randomLong(),
randomBoolean() ? null : randomDouble(),
randomBoolean() ? null : randomDouble(),
randomBoolean() ? null : randomDouble());
}

@Override
public TimingStats createTestInstance() {
return createTestInstance(randomAlphaOfLength(10));
}

@Override
protected TimingStats doParseInstance(XContentParser parser) {
return TimingStats.PARSER.apply(parser, null);
}

@Override
protected boolean supportsUnknownFields() {
return true;
}

public void testConstructor() {
TimingStats stats = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);

assertThat(stats.getJobId(), equalTo(JOB_ID));
assertThat(stats.getBucketCount(), equalTo(7L));
assertThat(stats.getMinBucketProcessingTimeMs(), equalTo(1.0));
assertThat(stats.getMaxBucketProcessingTimeMs(), equalTo(2.0));
assertThat(stats.getAvgBucketProcessingTimeMs(), equalTo(1.23));
}

public void testConstructor_NullValues() {
TimingStats stats = new TimingStats(JOB_ID, 7, null, null, null);

assertThat(stats.getJobId(), equalTo(JOB_ID));
assertThat(stats.getBucketCount(), equalTo(7L));
assertNull(stats.getMinBucketProcessingTimeMs());
assertNull(stats.getMaxBucketProcessingTimeMs());
assertNull(stats.getAvgBucketProcessingTimeMs());
}

public void testEquals() {
TimingStats stats1 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
TimingStats stats2 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
TimingStats stats3 = new TimingStats(JOB_ID, 7, 1.0, 3.0, 1.23);

assertTrue(stats1.equals(stats1));
assertTrue(stats1.equals(stats2));
assertFalse(stats2.equals(stats3));
}

public void testHashCode() {
TimingStats stats1 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
TimingStats stats2 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
TimingStats stats3 = new TimingStats(JOB_ID, 7, 1.0, 3.0, 1.23);

assertEquals(stats1.hashCode(), stats1.hashCode());
assertEquals(stats1.hashCode(), stats2.hashCode());
assertNotEquals(stats2.hashCode(), stats3.hashCode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.elasticsearch.client.ml.job.process.DataCountsTests;
import org.elasticsearch.client.ml.job.process.ModelSizeStats;
import org.elasticsearch.client.ml.job.process.ModelSizeStatsTests;
import org.elasticsearch.client.ml.job.process.TimingStats;
import org.elasticsearch.client.ml.job.process.TimingStatsTests;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.client.ml.job.config.JobState;
Expand All @@ -42,12 +44,14 @@ public static JobStats createRandomInstance() {
DataCounts dataCounts = DataCountsTests.createTestInstance(jobId);

ModelSizeStats modelSizeStats = randomBoolean() ? ModelSizeStatsTests.createRandomized() : null;
TimingStats timingStats = randomBoolean() ? TimingStatsTests.createTestInstance(jobId) : null;
ForecastStats forecastStats = randomBoolean() ? ForecastStatsTests.createRandom(1, 22) : null;
NodeAttributes nodeAttributes = randomBoolean() ? NodeAttributesTests.createRandom() : null;
String assigmentExplanation = randomBoolean() ? randomAlphaOfLength(10) : null;
TimeValue openTime = randomBoolean() ? TimeValue.timeValueMillis(randomIntBetween(1, 10000)) : null;

return new JobStats(jobId, dataCounts, state, modelSizeStats, forecastStats, nodeAttributes, assigmentExplanation, openTime);
return new JobStats(
jobId, dataCounts, state, modelSizeStats, timingStats, forecastStats, nodeAttributes, assigmentExplanation, openTime);
}

@Override
Expand Down
Loading

0 comments on commit 38f3a74

Please sign in to comment.