Skip to content

Commit b2613a1

Browse files
authored
[7.x] Report exponential_avg_bucket_processing_time which gives more weight to recent buckets (#43189) (#43263)
1 parent a191eba commit b2613a1

File tree

15 files changed

+299
-104
lines changed

15 files changed

+299
-104
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/process/TimingStats.java

+25-4
Original file line numberDiff line numberDiff line change
@@ -42,38 +42,45 @@ public class TimingStats implements ToXContentObject {
4242
public static final ParseField MIN_BUCKET_PROCESSING_TIME_MS = new ParseField("minimum_bucket_processing_time_ms");
4343
public static final ParseField MAX_BUCKET_PROCESSING_TIME_MS = new ParseField("maximum_bucket_processing_time_ms");
4444
public static final ParseField AVG_BUCKET_PROCESSING_TIME_MS = new ParseField("average_bucket_processing_time_ms");
45+
public static final ParseField EXPONENTIAL_AVG_BUCKET_PROCESSING_TIME_MS =
46+
new ParseField("exponential_average_bucket_processing_time_ms");
4547

4648
public static final ConstructingObjectParser<TimingStats, Void> PARSER =
4749
new ConstructingObjectParser<>(
4850
"timing_stats",
4951
true,
50-
args -> new TimingStats((String) args[0], (long) args[1], (Double) args[2], (Double) args[3], (Double) args[4]));
52+
args ->
53+
new TimingStats((String) args[0], (long) args[1], (Double) args[2], (Double) args[3], (Double) args[4], (Double) args[5]));
5154

5255
static {
5356
PARSER.declareString(constructorArg(), Job.ID);
5457
PARSER.declareLong(constructorArg(), BUCKET_COUNT);
5558
PARSER.declareDouble(optionalConstructorArg(), MIN_BUCKET_PROCESSING_TIME_MS);
5659
PARSER.declareDouble(optionalConstructorArg(), MAX_BUCKET_PROCESSING_TIME_MS);
5760
PARSER.declareDouble(optionalConstructorArg(), AVG_BUCKET_PROCESSING_TIME_MS);
61+
PARSER.declareDouble(optionalConstructorArg(), EXPONENTIAL_AVG_BUCKET_PROCESSING_TIME_MS);
5862
}
5963

6064
private final String jobId;
6165
private long bucketCount;
6266
private Double minBucketProcessingTimeMs;
6367
private Double maxBucketProcessingTimeMs;
6468
private Double avgBucketProcessingTimeMs;
69+
private Double exponentialAvgBucketProcessingTimeMs;
6570

6671
public TimingStats(
6772
String jobId,
6873
long bucketCount,
6974
@Nullable Double minBucketProcessingTimeMs,
7075
@Nullable Double maxBucketProcessingTimeMs,
71-
@Nullable Double avgBucketProcessingTimeMs) {
76+
@Nullable Double avgBucketProcessingTimeMs,
77+
@Nullable Double exponentialAvgBucketProcessingTimeMs) {
7278
this.jobId = jobId;
7379
this.bucketCount = bucketCount;
7480
this.minBucketProcessingTimeMs = minBucketProcessingTimeMs;
7581
this.maxBucketProcessingTimeMs = maxBucketProcessingTimeMs;
7682
this.avgBucketProcessingTimeMs = avgBucketProcessingTimeMs;
83+
this.exponentialAvgBucketProcessingTimeMs = exponentialAvgBucketProcessingTimeMs;
7784
}
7885

7986
public String getJobId() {
@@ -96,6 +103,10 @@ public Double getAvgBucketProcessingTimeMs() {
96103
return avgBucketProcessingTimeMs;
97104
}
98105

106+
public Double getExponentialAvgBucketProcessingTimeMs() {
107+
return exponentialAvgBucketProcessingTimeMs;
108+
}
109+
99110
@Override
100111
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
101112
builder.startObject();
@@ -110,6 +121,9 @@ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params par
110121
if (avgBucketProcessingTimeMs != null) {
111122
builder.field(AVG_BUCKET_PROCESSING_TIME_MS.getPreferredName(), avgBucketProcessingTimeMs);
112123
}
124+
if (exponentialAvgBucketProcessingTimeMs != null) {
125+
builder.field(EXPONENTIAL_AVG_BUCKET_PROCESSING_TIME_MS.getPreferredName(), exponentialAvgBucketProcessingTimeMs);
126+
}
113127
builder.endObject();
114128
return builder;
115129
}
@@ -123,12 +137,19 @@ public boolean equals(Object o) {
123137
&& this.bucketCount == that.bucketCount
124138
&& Objects.equals(this.minBucketProcessingTimeMs, that.minBucketProcessingTimeMs)
125139
&& Objects.equals(this.maxBucketProcessingTimeMs, that.maxBucketProcessingTimeMs)
126-
&& Objects.equals(this.avgBucketProcessingTimeMs, that.avgBucketProcessingTimeMs);
140+
&& Objects.equals(this.avgBucketProcessingTimeMs, that.avgBucketProcessingTimeMs)
141+
&& Objects.equals(this.exponentialAvgBucketProcessingTimeMs, that.exponentialAvgBucketProcessingTimeMs);
127142
}
128143

129144
@Override
130145
public int hashCode() {
131-
return Objects.hash(jobId, bucketCount, minBucketProcessingTimeMs, maxBucketProcessingTimeMs, avgBucketProcessingTimeMs);
146+
return Objects.hash(
147+
jobId,
148+
bucketCount,
149+
minBucketProcessingTimeMs,
150+
maxBucketProcessingTimeMs,
151+
avgBucketProcessingTimeMs,
152+
exponentialAvgBucketProcessingTimeMs);
132153
}
133154

134155
@Override

client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/process/TimingStatsTests.java

+15-11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.test.AbstractXContentTestCase;
2323

2424
import static org.hamcrest.Matchers.equalTo;
25+
import static org.hamcrest.Matchers.nullValue;
2526

2627
public class TimingStatsTests extends AbstractXContentTestCase<TimingStats> {
2728

@@ -33,6 +34,7 @@ public static TimingStats createTestInstance(String jobId) {
3334
randomLong(),
3435
randomBoolean() ? null : randomDouble(),
3536
randomBoolean() ? null : randomDouble(),
37+
randomBoolean() ? null : randomDouble(),
3638
randomBoolean() ? null : randomDouble());
3739
}
3840

@@ -52,39 +54,41 @@ protected boolean supportsUnknownFields() {
5254
}
5355

5456
public void testConstructor() {
55-
TimingStats stats = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
57+
TimingStats stats = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23, 7.89);
5658

5759
assertThat(stats.getJobId(), equalTo(JOB_ID));
5860
assertThat(stats.getBucketCount(), equalTo(7L));
5961
assertThat(stats.getMinBucketProcessingTimeMs(), equalTo(1.0));
6062
assertThat(stats.getMaxBucketProcessingTimeMs(), equalTo(2.0));
6163
assertThat(stats.getAvgBucketProcessingTimeMs(), equalTo(1.23));
64+
assertThat(stats.getExponentialAvgBucketProcessingTimeMs(), equalTo(7.89));
6265
}
6366

6467
public void testConstructor_NullValues() {
65-
TimingStats stats = new TimingStats(JOB_ID, 7, null, null, null);
68+
TimingStats stats = new TimingStats(JOB_ID, 7, null, null, null, null);
6669

6770
assertThat(stats.getJobId(), equalTo(JOB_ID));
6871
assertThat(stats.getBucketCount(), equalTo(7L));
69-
assertNull(stats.getMinBucketProcessingTimeMs());
70-
assertNull(stats.getMaxBucketProcessingTimeMs());
71-
assertNull(stats.getAvgBucketProcessingTimeMs());
72+
assertThat(stats.getMinBucketProcessingTimeMs(), nullValue());
73+
assertThat(stats.getMaxBucketProcessingTimeMs(), nullValue());
74+
assertThat(stats.getAvgBucketProcessingTimeMs(), nullValue());
75+
assertThat(stats.getExponentialAvgBucketProcessingTimeMs(), nullValue());
7276
}
7377

7478
public void testEquals() {
75-
TimingStats stats1 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
76-
TimingStats stats2 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
77-
TimingStats stats3 = new TimingStats(JOB_ID, 7, 1.0, 3.0, 1.23);
79+
TimingStats stats1 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23, 7.89);
80+
TimingStats stats2 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23, 7.89);
81+
TimingStats stats3 = new TimingStats(JOB_ID, 7, 1.0, 3.0, 1.23, 7.89);
7882

7983
assertTrue(stats1.equals(stats1));
8084
assertTrue(stats1.equals(stats2));
8185
assertFalse(stats2.equals(stats3));
8286
}
8387

8488
public void testHashCode() {
85-
TimingStats stats1 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
86-
TimingStats stats2 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23);
87-
TimingStats stats3 = new TimingStats(JOB_ID, 7, 1.0, 3.0, 1.23);
89+
TimingStats stats1 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23, 7.89);
90+
TimingStats stats2 = new TimingStats(JOB_ID, 7, 1.0, 2.0, 1.23, 7.89);
91+
TimingStats stats3 = new TimingStats(JOB_ID, 7, 1.0, 3.0, 1.23, 7.89);
8892

8993
assertEquals(stats1.hashCode(), stats1.hashCode());
9094
assertEquals(stats1.hashCode(), stats2.hashCode());

docs/reference/ml/apis/get-job-stats.asciidoc

+8-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,14 @@ The API returns the following results:
105105
"log_time": 1491948163000,
106106
"timestamp": 1455234600000
107107
},
108-
"state": "closed"
108+
"state": "closed",
109+
"timing_stats": {
110+
"job_id": "farequote",
111+
"minimum_bucket_processing_time_ms": 0.0,
112+
"maximum_bucket_processing_time_ms": 15.0,
113+
"average_bucket_processing_time_ms": 8.75,
114+
"exponential_average_bucket_processing_time_ms": 6.1435899
115+
}
109116
}
110117
]
111118
}

docs/reference/ml/apis/jobcounts.asciidoc

+31-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ progress of a job.
1919

2020
`model_size_stats`::
2121
(object) An object that provides information about the size and contents of the model.
22-
See <<ml-modelsizestats,model size stats objects>>
22+
See <<ml-modelsizestats,model size stats objects>>.
2323

2424
`forecasts_stats`::
2525
(object) An object that provides statistical information about forecasts
26-
of this job. See <<ml-forecastsstats, forecasts stats objects>>
26+
of this job. See <<ml-forecastsstats, forecasts stats objects>>.
27+
28+
`timing_stats`::
29+
(object) An object that provides statistical information about timing aspect
30+
of this job. See <<ml-timingstats, timing stats objects>>.
2731

2832
`node`::
2933
(object) For open jobs only, contains information about the node where the
@@ -209,6 +213,31 @@ The `forecasts_stats` object shows statistics about forecasts. It has the follow
209213
NOTE: `memory_bytes`, `records`, `processing_time_ms` and `status` require at least 1 forecast, otherwise
210214
these fields are omitted.
211215

216+
[float]
217+
[[ml-timingstats]]
218+
==== Timing Stats Objects
219+
220+
The `timing_stats` object shows timing-related statistics about the job's progress. It has the following properties:
221+
222+
`job_id`::
223+
(string) A numerical character string that uniquely identifies the job.
224+
225+
`bucket_count`::
226+
(long) The number of buckets processed.
227+
228+
`minimum_bucket_processing_time_ms`::
229+
(double) Minimum among all bucket processing times in milliseconds.
230+
231+
`maximum_bucket_processing_time_ms`::
232+
(double) Maximum among all bucket processing times in milliseconds.
233+
234+
`average_bucket_processing_time_ms`::
235+
(double) Average of all bucket processing times in milliseconds.
236+
237+
`exponential_average_bucket_processing_time_ms`::
238+
(double) Exponential moving average of all bucket processing times in milliseconds.
239+
240+
212241
[float]
213242
[[ml-stats-node]]
214243
==== Node Objects

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/persistence/ElasticsearchMappings.java

+3
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,9 @@ private static void addTimingStatsExceptBucketCountMapping(XContentBuilder build
863863
.endObject()
864864
.startObject(TimingStats.AVG_BUCKET_PROCESSING_TIME_MS.getPreferredName())
865865
.field(TYPE, DOUBLE)
866+
.endObject()
867+
.startObject(TimingStats.EXPONENTIAL_AVERAGE_BUCKET_PROCESSING_TIME_MS.getPreferredName())
868+
.field(TYPE, DOUBLE)
866869
.endObject();
867870
}
868871

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/process/autodetect/state/TimingStats.java

+50-8
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,25 @@ public class TimingStats implements ToXContentObject, Writeable {
3131
public static final ParseField MIN_BUCKET_PROCESSING_TIME_MS = new ParseField("minimum_bucket_processing_time_ms");
3232
public static final ParseField MAX_BUCKET_PROCESSING_TIME_MS = new ParseField("maximum_bucket_processing_time_ms");
3333
public static final ParseField AVG_BUCKET_PROCESSING_TIME_MS = new ParseField("average_bucket_processing_time_ms");
34+
public static final ParseField EXPONENTIAL_AVERAGE_BUCKET_PROCESSING_TIME_MS =
35+
new ParseField("exponential_average_bucket_processing_time_ms");
3436

3537
public static final ParseField TYPE = new ParseField("timing_stats");
3638

3739
public static final ConstructingObjectParser<TimingStats, Void> PARSER =
3840
new ConstructingObjectParser<>(
3941
TYPE.getPreferredName(),
4042
true,
41-
args -> new TimingStats((String) args[0], (long) args[1], (Double) args[2], (Double) args[3], (Double) args[4]));
43+
args ->
44+
new TimingStats((String) args[0], (long) args[1], (Double) args[2], (Double) args[3], (Double) args[4], (Double) args[5]));
4245

4346
static {
4447
PARSER.declareString(constructorArg(), Job.ID);
4548
PARSER.declareLong(constructorArg(), BUCKET_COUNT);
4649
PARSER.declareDouble(optionalConstructorArg(), MIN_BUCKET_PROCESSING_TIME_MS);
4750
PARSER.declareDouble(optionalConstructorArg(), MAX_BUCKET_PROCESSING_TIME_MS);
4851
PARSER.declareDouble(optionalConstructorArg(), AVG_BUCKET_PROCESSING_TIME_MS);
52+
PARSER.declareDouble(optionalConstructorArg(), EXPONENTIAL_AVERAGE_BUCKET_PROCESSING_TIME_MS);
4953
}
5054

5155
public static String documentId(String jobId) {
@@ -57,26 +61,35 @@ public static String documentId(String jobId) {
5761
private Double minBucketProcessingTimeMs;
5862
private Double maxBucketProcessingTimeMs;
5963
private Double avgBucketProcessingTimeMs;
64+
private Double exponentialAvgBucketProcessingTimeMs;
6065

6166
public TimingStats(
6267
String jobId,
6368
long bucketCount,
6469
@Nullable Double minBucketProcessingTimeMs,
6570
@Nullable Double maxBucketProcessingTimeMs,
66-
@Nullable Double avgBucketProcessingTimeMs) {
71+
@Nullable Double avgBucketProcessingTimeMs,
72+
@Nullable Double exponentialAvgBucketProcessingTimeMs) {
6773
this.jobId = jobId;
6874
this.bucketCount = bucketCount;
6975
this.minBucketProcessingTimeMs = minBucketProcessingTimeMs;
7076
this.maxBucketProcessingTimeMs = maxBucketProcessingTimeMs;
7177
this.avgBucketProcessingTimeMs = avgBucketProcessingTimeMs;
78+
this.exponentialAvgBucketProcessingTimeMs = exponentialAvgBucketProcessingTimeMs;
7279
}
7380

7481
public TimingStats(String jobId) {
75-
this(jobId, 0, null, null, null);
82+
this(jobId, 0, null, null, null, null);
7683
}
7784

7885
public TimingStats(TimingStats lhs) {
79-
this(lhs.jobId, lhs.bucketCount, lhs.minBucketProcessingTimeMs, lhs.maxBucketProcessingTimeMs, lhs.avgBucketProcessingTimeMs);
86+
this(
87+
lhs.jobId,
88+
lhs.bucketCount,
89+
lhs.minBucketProcessingTimeMs,
90+
lhs.maxBucketProcessingTimeMs,
91+
lhs.avgBucketProcessingTimeMs,
92+
lhs.exponentialAvgBucketProcessingTimeMs);
8093
}
8194

8295
public TimingStats(StreamInput in) throws IOException {
@@ -85,6 +98,7 @@ public TimingStats(StreamInput in) throws IOException {
8598
this.minBucketProcessingTimeMs = in.readOptionalDouble();
8699
this.maxBucketProcessingTimeMs = in.readOptionalDouble();
87100
this.avgBucketProcessingTimeMs = in.readOptionalDouble();
101+
this.exponentialAvgBucketProcessingTimeMs = in.readOptionalDouble();
88102
}
89103

90104
public String getJobId() {
@@ -107,12 +121,16 @@ public Double getAvgBucketProcessingTimeMs() {
107121
return avgBucketProcessingTimeMs;
108122
}
109123

124+
public Double getExponentialAvgBucketProcessingTimeMs() {
125+
return exponentialAvgBucketProcessingTimeMs;
126+
}
127+
110128
/**
111129
* Updates the statistics (min, max, avg) for the given data point (bucket processing time).
112130
*/
113131
public void updateStats(double bucketProcessingTimeMs) {
114132
if (bucketProcessingTimeMs < 0.0) {
115-
throw new IllegalArgumentException("bucketProcessingTimeMs must be positive, was: " + bucketProcessingTimeMs);
133+
throw new IllegalArgumentException("bucketProcessingTimeMs must be non-negative, was: " + bucketProcessingTimeMs);
116134
}
117135
if (minBucketProcessingTimeMs == null || bucketProcessingTimeMs < minBucketProcessingTimeMs) {
118136
minBucketProcessingTimeMs = bucketProcessingTimeMs;
@@ -127,16 +145,29 @@ public void updateStats(double bucketProcessingTimeMs) {
127145
// bucket processing times.
128146
avgBucketProcessingTimeMs = (bucketCount * avgBucketProcessingTimeMs + bucketProcessingTimeMs) / (bucketCount + 1);
129147
}
148+
if (exponentialAvgBucketProcessingTimeMs == null) {
149+
exponentialAvgBucketProcessingTimeMs = bucketProcessingTimeMs;
150+
} else {
151+
// Calculate the exponential moving average (see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average) of
152+
// bucket processing times.
153+
exponentialAvgBucketProcessingTimeMs = (1 - ALPHA) * exponentialAvgBucketProcessingTimeMs + ALPHA * bucketProcessingTimeMs;
154+
}
130155
bucketCount++;
131156
}
132157

158+
/**
159+
* Constant smoothing factor used for calculating exponential moving average. Represents the degree of weighting decrease.
160+
*/
161+
private static double ALPHA = 0.01;
162+
133163
@Override
134164
public void writeTo(StreamOutput out) throws IOException {
135165
out.writeString(jobId);
136166
out.writeLong(bucketCount);
137167
out.writeOptionalDouble(minBucketProcessingTimeMs);
138168
out.writeOptionalDouble(maxBucketProcessingTimeMs);
139169
out.writeOptionalDouble(avgBucketProcessingTimeMs);
170+
out.writeOptionalDouble(exponentialAvgBucketProcessingTimeMs);
140171
}
141172

142173
@Override
@@ -153,6 +184,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
153184
if (avgBucketProcessingTimeMs != null) {
154185
builder.field(AVG_BUCKET_PROCESSING_TIME_MS.getPreferredName(), avgBucketProcessingTimeMs);
155186
}
187+
if (exponentialAvgBucketProcessingTimeMs != null) {
188+
builder.field(EXPONENTIAL_AVERAGE_BUCKET_PROCESSING_TIME_MS.getPreferredName(), exponentialAvgBucketProcessingTimeMs);
189+
}
156190
builder.endObject();
157191
return builder;
158192
}
@@ -166,12 +200,19 @@ public boolean equals(Object o) {
166200
&& this.bucketCount == that.bucketCount
167201
&& Objects.equals(this.minBucketProcessingTimeMs, that.minBucketProcessingTimeMs)
168202
&& Objects.equals(this.maxBucketProcessingTimeMs, that.maxBucketProcessingTimeMs)
169-
&& Objects.equals(this.avgBucketProcessingTimeMs, that.avgBucketProcessingTimeMs);
203+
&& Objects.equals(this.avgBucketProcessingTimeMs, that.avgBucketProcessingTimeMs)
204+
&& Objects.equals(this.exponentialAvgBucketProcessingTimeMs, that.exponentialAvgBucketProcessingTimeMs);
170205
}
171206

172207
@Override
173208
public int hashCode() {
174-
return Objects.hash(jobId, bucketCount, minBucketProcessingTimeMs, maxBucketProcessingTimeMs, avgBucketProcessingTimeMs);
209+
return Objects.hash(
210+
jobId,
211+
bucketCount,
212+
minBucketProcessingTimeMs,
213+
maxBucketProcessingTimeMs,
214+
avgBucketProcessingTimeMs,
215+
exponentialAvgBucketProcessingTimeMs);
175216
}
176217

177218
@Override
@@ -185,7 +226,8 @@ public String toString() {
185226
public static boolean differSignificantly(TimingStats stats1, TimingStats stats2) {
186227
return differSignificantly(stats1.minBucketProcessingTimeMs, stats2.minBucketProcessingTimeMs)
187228
|| differSignificantly(stats1.maxBucketProcessingTimeMs, stats2.maxBucketProcessingTimeMs)
188-
|| differSignificantly(stats1.avgBucketProcessingTimeMs, stats2.avgBucketProcessingTimeMs);
229+
|| differSignificantly(stats1.avgBucketProcessingTimeMs, stats2.avgBucketProcessingTimeMs)
230+
|| differSignificantly(stats1.exponentialAvgBucketProcessingTimeMs, stats2.exponentialAvgBucketProcessingTimeMs);
189231
}
190232

191233
/**

0 commit comments

Comments
 (0)