Skip to content

Commit 4bd217c

Browse files
authored
Add pluggable XContentBuilder writers and human readable writers (#29120)
* Add pluggable XContentBuilder writers and human readable writers This adds the ability to use SPI to plug in writers for XContentBuilder. By implementing the XContentBuilderProvider class we can allow Elasticsearch to plug in different ways to encode types to JSON. Important caveat for this, we should always try to have the class implement `ToXContentFragment` first, however, in the case of classes from our dependencies (think Joda classes or Lucene classes) we need a way to specify writers for these classes. This also makes the human-readable field writers generic and pluggable, so that we no longer need to tie XContentBuilder to things like `TimeValue` and `ByteSizeValue`. Contained as part of this moves all the TimeValue human readable fields to the new `humanReadableField` method. A future commit will move the `ByteSizeValue` calls over to this method. Relates to #28504
1 parent 701625b commit 4bd217c

32 files changed

+202
-90
lines changed

server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
245245
builder.field(DELAYED_UNASSIGNED_SHARDS, getDelayedUnassignedShards());
246246
builder.field(NUMBER_OF_PENDING_TASKS, getNumberOfPendingTasks());
247247
builder.field(NUMBER_OF_IN_FLIGHT_FETCH, getNumberOfInFlightFetch());
248-
builder.timeValueField(TASK_MAX_WAIT_TIME_IN_QUEUE_IN_MILLIS, TASK_MAX_WAIT_TIME_IN_QUEUE, getTaskMaxWaitingTime());
248+
builder.humanReadableField(TASK_MAX_WAIT_TIME_IN_QUEUE_IN_MILLIS, TASK_MAX_WAIT_TIME_IN_QUEUE, getTaskMaxWaitingTime());
249249
builder.percentageField(ACTIVE_SHARDS_PERCENT_AS_NUMBER, ACTIVE_SHARDS_PERCENT, getActiveShardsPercent());
250250

251251
String level = params.param("level", "cluster");

server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
6868
builder.field("version", nodeInfo.getVersion());
6969
builder.field("build_hash", nodeInfo.getBuild().shortHash());
7070
if (nodeInfo.getTotalIndexingBuffer() != null) {
71-
builder.byteSizeField("total_indexing_buffer", "total_indexing_buffer_in_bytes", nodeInfo.getTotalIndexingBuffer());
71+
builder.humanReadableField("total_indexing_buffer", "total_indexing_buffer_in_bytes", nodeInfo.getTotalIndexingBuffer());
7272
}
7373

7474
builder.startArray("roles");

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/SnapshotStats.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.common.io.stream.StreamInput;
2323
import org.elasticsearch.common.io.stream.StreamOutput;
2424
import org.elasticsearch.common.io.stream.Streamable;
25+
import org.elasticsearch.common.unit.TimeValue;
2526
import org.elasticsearch.common.xcontent.ToXContent;
2627
import org.elasticsearch.common.xcontent.ToXContentFragment;
2728
import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -143,7 +144,7 @@ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params par
143144
builder.byteSizeField(Fields.TOTAL_SIZE_IN_BYTES, Fields.TOTAL_SIZE, getTotalSize());
144145
builder.byteSizeField(Fields.PROCESSED_SIZE_IN_BYTES, Fields.PROCESSED_SIZE, getProcessedSize());
145146
builder.field(Fields.START_TIME_IN_MILLIS, getStartTime());
146-
builder.timeValueField(Fields.TIME_IN_MILLIS, Fields.TIME, getTime());
147+
builder.humanReadableField(Fields.TIME_IN_MILLIS, Fields.TIME, new TimeValue(getTime()));
147148
builder.endObject();
148149
return builder;
149150
}

server/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsNodes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ static final class Fields {
488488
@Override
489489
public XContentBuilder toXContent(XContentBuilder builder, Params params)
490490
throws IOException {
491-
builder.timeValueField(Fields.MAX_UPTIME_IN_MILLIS, Fields.MAX_UPTIME, maxUptime);
491+
builder.humanReadableField(Fields.MAX_UPTIME_IN_MILLIS, Fields.MAX_UPTIME, new TimeValue(maxUptime));
492492
builder.startArray(Fields.VERSIONS);
493493
for (ObjectIntCursor<JvmVersion> v : versions) {
494494
builder.startObject();

server/src/main/java/org/elasticsearch/action/admin/indices/segments/IndicesSegmentResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ static void toXContent(XContentBuilder builder, Sort sort) throws IOException {
198198
static void toXContent(XContentBuilder builder, Accountable tree) throws IOException {
199199
builder.startObject();
200200
builder.field(Fields.DESCRIPTION, tree.toString());
201-
builder.byteSizeField(Fields.SIZE_IN_BYTES, Fields.SIZE, new ByteSizeValue(tree.ramBytesUsed()));
201+
builder.humanReadableField(Fields.SIZE_IN_BYTES, Fields.SIZE, new ByteSizeValue(tree.ramBytesUsed()));
202202
Collection<Accountable> children = tree.getChildResources();
203203
if (children.isEmpty() == false) {
204204
builder.startArray(Fields.CHILDREN);

server/src/main/java/org/elasticsearch/cluster/SnapshotDeletionsInProgress.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.common.io.stream.StreamInput;
2525
import org.elasticsearch.common.io.stream.StreamOutput;
2626
import org.elasticsearch.common.io.stream.Writeable;
27+
import org.elasticsearch.common.unit.TimeValue;
2728
import org.elasticsearch.common.xcontent.XContentBuilder;
2829
import org.elasticsearch.snapshots.Snapshot;
2930

@@ -145,7 +146,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
145146
{
146147
builder.field("repository", entry.snapshot.getRepository());
147148
builder.field("snapshot", entry.snapshot.getSnapshotId().getName());
148-
builder.timeValueField("start_time_millis", "start_time", entry.startTime);
149+
builder.humanReadableField("start_time_millis", "start_time", new TimeValue(entry.startTime));
149150
builder.field("repository_state_id", entry.repositoryStateId);
150151
}
151152
builder.endObject();

server/src/main/java/org/elasticsearch/cluster/SnapshotsInProgress.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.common.collect.ImmutableOpenMap;
2828
import org.elasticsearch.common.io.stream.StreamInput;
2929
import org.elasticsearch.common.io.stream.StreamOutput;
30+
import org.elasticsearch.common.unit.TimeValue;
3031
import org.elasticsearch.common.xcontent.ToXContent;
3132
import org.elasticsearch.common.xcontent.XContentBuilder;
3233
import org.elasticsearch.index.shard.ShardId;
@@ -512,7 +513,7 @@ public void toXContent(Entry entry, XContentBuilder builder, ToXContent.Params p
512513
}
513514
}
514515
builder.endArray();
515-
builder.timeValueField(START_TIME_MILLIS, START_TIME, entry.startTime());
516+
builder.humanReadableField(START_TIME_MILLIS, START_TIME, new TimeValue(entry.startTime()));
516517
builder.field(REPOSITORY_STATE_ID, entry.getRepositoryStateId());
517518
builder.startArray(SHARDS);
518519
{

server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocateUnassignedDecision.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,10 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
289289
builder.field("allocation_id", allocationId);
290290
}
291291
if (allocationStatus == AllocationStatus.DELAYED_ALLOCATION) {
292-
builder.timeValueField("configured_delay_in_millis", "configured_delay", TimeValue.timeValueMillis(configuredDelayInMillis));
293-
builder.timeValueField("remaining_delay_in_millis", "remaining_delay", TimeValue.timeValueMillis(remainingDelayInMillis));
292+
builder.humanReadableField("configured_delay_in_millis", "configured_delay",
293+
TimeValue.timeValueMillis(configuredDelayInMillis));
294+
builder.humanReadableField("remaining_delay_in_millis", "remaining_delay",
295+
TimeValue.timeValueMillis(remainingDelayInMillis));
294296
}
295297
nodeDecisionsToXContent(nodeDecisions, builder, params);
296298
return builder;

server/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
import java.util.Locale;
4444
import java.util.Map;
4545
import java.util.Objects;
46+
import java.util.ServiceLoader;
4647
import java.util.Set;
47-
import java.util.concurrent.TimeUnit;
4848

4949
/**
5050
* A utility to build XContent (ie json).
@@ -85,6 +85,7 @@ public static XContentBuilder builder(XContent xContent, Set<String> includes, S
8585
public static final DateTimeFormatter DEFAULT_DATE_PRINTER = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC);
8686

8787
private static final Map<Class<?>, Writer> WRITERS;
88+
private static final Map<Class<?>, HumanReadableTransformer> HUMAN_READABLE_TRANSFORMERS;
8889
static {
8990
Map<Class<?>, Writer> writers = new HashMap<>();
9091
writers.put(Boolean.class, (b, v) -> b.value((Boolean) v));
@@ -105,14 +106,43 @@ public static XContentBuilder builder(XContent xContent, Set<String> includes, S
105106
writers.put(String.class, (b, v) -> b.value((String) v));
106107
writers.put(String[].class, (b, v) -> b.values((String[]) v));
107108

109+
110+
Map<Class<?>, HumanReadableTransformer> humanReadableTransformer = new HashMap<>();
111+
// These will be moved to a different class at a later time to decouple them from XContentBuilder
112+
humanReadableTransformer.put(TimeValue.class, v -> ((TimeValue) v).millis());
113+
humanReadableTransformer.put(ByteSizeValue.class, v -> ((ByteSizeValue) v).getBytes());
114+
115+
// Load pluggable extensions
116+
for (XContentBuilderExtension service : ServiceLoader.load(XContentBuilderExtension.class)) {
117+
Map<Class<?>, Writer> addlWriters = service.getXContentWriters();
118+
Map<Class<?>, HumanReadableTransformer> addlTransformers = service.getXContentHumanReadableTransformers();
119+
120+
addlWriters.forEach((key, value) -> Objects.requireNonNull(value,
121+
"invalid null xcontent writer for class " + key));
122+
addlTransformers.forEach((key, value) -> Objects.requireNonNull(value,
123+
"invalid null xcontent transformer for human readable class " + key));
124+
125+
writers.putAll(addlWriters);
126+
humanReadableTransformer.putAll(addlTransformers);
127+
}
128+
108129
WRITERS = Collections.unmodifiableMap(writers);
130+
HUMAN_READABLE_TRANSFORMERS = Collections.unmodifiableMap(humanReadableTransformer);
109131
}
110132

111133
@FunctionalInterface
112-
private interface Writer {
134+
public interface Writer {
113135
void write(XContentBuilder builder, Object value) throws IOException;
114136
}
115137

138+
/**
139+
* Interface for transforming complex objects into their "raw" equivalents for human-readable fields
140+
*/
141+
@FunctionalInterface
142+
public interface HumanReadableTransformer {
143+
Object rawValue(Object value) throws IOException;
144+
}
145+
116146
/**
117147
* XContentGenerator used to build the XContent object
118148
*/
@@ -856,33 +886,30 @@ private XContentBuilder value(Iterable<?> values, boolean ensureNoSelfReferences
856886
}
857887

858888
////////////////////////////////////////////////////////////////////////////
859-
// Misc.
889+
// Human readable fields
890+
//
891+
// These are fields that have a "raw" value and a "human readable" value,
892+
// such as time values or byte sizes. The human readable variant is only
893+
// used if the humanReadable flag has been set
860894
//////////////////////////////////
861895

862-
public XContentBuilder timeValueField(String rawFieldName, String readableFieldName, TimeValue timeValue) throws IOException {
896+
public XContentBuilder humanReadableField(String rawFieldName, String readableFieldName, Object value) throws IOException {
863897
if (humanReadable) {
864-
field(readableFieldName, timeValue.toString());
898+
field(readableFieldName, Objects.toString(value));
865899
}
866-
field(rawFieldName, timeValue.millis());
867-
return this;
868-
}
869-
870-
public XContentBuilder timeValueField(String rawFieldName, String readableFieldName, long rawTime) throws IOException {
871-
if (humanReadable) {
872-
field(readableFieldName, new TimeValue(rawTime).toString());
900+
HumanReadableTransformer transformer = HUMAN_READABLE_TRANSFORMERS.get(value.getClass());
901+
if (transformer != null) {
902+
Object rawValue = transformer.rawValue(value);
903+
field(rawFieldName, rawValue);
904+
} else {
905+
throw new IllegalArgumentException("no raw transformer found for class " + value.getClass());
873906
}
874-
field(rawFieldName, rawTime);
875907
return this;
876908
}
877909

878-
public XContentBuilder timeValueField(String rawFieldName, String readableFieldName, long rawTime, TimeUnit timeUnit) throws
879-
IOException {
880-
if (humanReadable) {
881-
field(readableFieldName, new TimeValue(rawTime, timeUnit).toString());
882-
}
883-
field(rawFieldName, rawTime);
884-
return this;
885-
}
910+
////////////////////////////////////////////////////////////////////////////
911+
// Misc.
912+
//////////////////////////////////
886913

887914

888915
public XContentBuilder percentageField(String rawFieldName, String readableFieldName, double percentage) throws IOException {
@@ -893,14 +920,6 @@ public XContentBuilder percentageField(String rawFieldName, String readableField
893920
return this;
894921
}
895922

896-
public XContentBuilder byteSizeField(String rawFieldName, String readableFieldName, ByteSizeValue byteSizeValue) throws IOException {
897-
if (humanReadable) {
898-
field(readableFieldName, byteSizeValue.toString());
899-
}
900-
field(rawFieldName, byteSizeValue.getBytes());
901-
return this;
902-
}
903-
904923
public XContentBuilder byteSizeField(String rawFieldName, String readableFieldName, long rawSize) throws IOException {
905924
if (humanReadable) {
906925
field(readableFieldName, new ByteSizeValue(rawSize).toString());
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.common.xcontent;
21+
22+
import java.util.Map;
23+
24+
/**
25+
* This interface provides a way for non-JDK classes to plug in a way to serialize to xcontent.
26+
*
27+
* It is <b>greatly</b> preferred that you implement {@link ToXContentFragment}
28+
* in the class for encoding, however, in some situations you may not own the
29+
* class, in which case you can add an implementation here for encoding it.
30+
*/
31+
public interface XContentBuilderExtension {
32+
33+
/**
34+
* Used for plugging in a generic writer for a class, for example, an example implementation:
35+
*
36+
* <pre>
37+
* {@code
38+
* Map<Class<?>, XContentBuilder.Writer> addlWriters = new HashMap<>();
39+
* addlWriters.put(BytesRef.class, (builder, value) -> b.value(((BytesRef) value).utf8String()));
40+
* return addlWriters;
41+
* }
42+
* </pre>
43+
*
44+
* @return a map of class name to writer
45+
*/
46+
Map<Class<?>, XContentBuilder.Writer> getXContentWriters();
47+
48+
/**
49+
* Used for plugging in a human readable version of a class's encoding. It is assumed that
50+
* the human readable equivalent is <b>always</b> behind the {@code toString()} method, so
51+
* this transformer returns the raw value to be used.
52+
*
53+
* An example implementation:
54+
*
55+
* <pre>
56+
* {@code
57+
* Map<Class<?>, XContentBuilder.HumanReadableTransformer> transformers = new HashMap<>();
58+
* transformers.put(ByteSizeValue.class, (value) -> ((ByteSizeValue) value).bytes());
59+
* }
60+
* </pre>
61+
* @return a map of class name to transformer used to retrieve raw value
62+
*/
63+
Map<Class<?>, XContentBuilder.HumanReadableTransformer> getXContentHumanReadableTransformers();
64+
}

server/src/main/java/org/elasticsearch/index/flush/FlushStats.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public TimeValue getTotalTime() {
8585
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
8686
builder.startObject(Fields.FLUSH);
8787
builder.field(Fields.TOTAL, total);
88-
builder.timeValueField(Fields.TOTAL_TIME_IN_MILLIS, Fields.TOTAL_TIME, totalTimeInMillis);
88+
builder.humanReadableField(Fields.TOTAL_TIME_IN_MILLIS, Fields.TOTAL_TIME, getTotalTime());
8989
builder.endObject();
9090
return builder;
9191
}

server/src/main/java/org/elasticsearch/index/get/GetStats.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@ public long current() {
110110
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
111111
builder.startObject(Fields.GET);
112112
builder.field(Fields.TOTAL, getCount());
113-
builder.timeValueField(Fields.TIME_IN_MILLIS, Fields.TIME, getTimeInMillis());
113+
builder.humanReadableField(Fields.TIME_IN_MILLIS, Fields.TIME, getTime());
114114
builder.field(Fields.EXISTS_TOTAL, existsCount);
115-
builder.timeValueField(Fields.EXISTS_TIME_IN_MILLIS, Fields.EXISTS_TIME, existsTimeInMillis);
115+
builder.humanReadableField(Fields.EXISTS_TIME_IN_MILLIS, Fields.EXISTS_TIME, getExistsTime());
116116
builder.field(Fields.MISSING_TOTAL, missingCount);
117-
builder.timeValueField(Fields.MISSING_TIME_IN_MILLIS, Fields.MISSING_TIME, missingTimeInMillis);
117+
builder.humanReadableField(Fields.MISSING_TIME_IN_MILLIS, Fields.MISSING_TIME, getMissingTime());
118118
builder.field(Fields.CURRENT, current);
119119
builder.endObject();
120120
return builder;

server/src/main/java/org/elasticsearch/index/merge/MergeStats.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
189189
builder.field(Fields.CURRENT_DOCS, currentNumDocs);
190190
builder.byteSizeField(Fields.CURRENT_SIZE_IN_BYTES, Fields.CURRENT_SIZE, currentSizeInBytes);
191191
builder.field(Fields.TOTAL, total);
192-
builder.timeValueField(Fields.TOTAL_TIME_IN_MILLIS, Fields.TOTAL_TIME, totalTimeInMillis);
192+
builder.humanReadableField(Fields.TOTAL_TIME_IN_MILLIS, Fields.TOTAL_TIME, getTotalTime());
193193
builder.field(Fields.TOTAL_DOCS, totalNumDocs);
194194
builder.byteSizeField(Fields.TOTAL_SIZE_IN_BYTES, Fields.TOTAL_SIZE, totalSizeInBytes);
195-
builder.timeValueField(Fields.TOTAL_STOPPED_TIME_IN_MILLIS, Fields.TOTAL_STOPPED_TIME, totalStoppedTimeInMillis);
196-
builder.timeValueField(Fields.TOTAL_THROTTLED_TIME_IN_MILLIS, Fields.TOTAL_THROTTLED_TIME, totalThrottledTimeInMillis);
195+
builder.humanReadableField(Fields.TOTAL_STOPPED_TIME_IN_MILLIS, Fields.TOTAL_STOPPED_TIME, getTotalStoppedTime());
196+
builder.humanReadableField(Fields.TOTAL_THROTTLED_TIME_IN_MILLIS, Fields.TOTAL_THROTTLED_TIME, getTotalThrottledTime());
197197
builder.byteSizeField(Fields.TOTAL_THROTTLE_BYTES_PER_SEC_IN_BYTES, Fields.TOTAL_THROTTLE_BYTES_PER_SEC, totalBytesPerSecAutoThrottle);
198198
builder.endObject();
199199
return builder;

server/src/main/java/org/elasticsearch/index/recovery/RecoveryStats.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
103103
builder.startObject(Fields.RECOVERY);
104104
builder.field(Fields.CURRENT_AS_SOURCE, currentAsSource());
105105
builder.field(Fields.CURRENT_AS_TARGET, currentAsTarget());
106-
builder.timeValueField(Fields.THROTTLE_TIME_IN_MILLIS, Fields.THROTTLE_TIME, throttleTime());
106+
builder.humanReadableField(Fields.THROTTLE_TIME_IN_MILLIS, Fields.THROTTLE_TIME, throttleTime());
107107
builder.endObject();
108108
return builder;
109109
}

server/src/main/java/org/elasticsearch/index/refresh/RefreshStats.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public int getListeners() {
9696
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
9797
builder.startObject("refresh");
9898
builder.field("total", total);
99-
builder.timeValueField("total_time_in_millis", "total_time", totalTimeInMillis);
99+
builder.humanReadableField("total_time_in_millis", "total_time", getTotalTime());
100100
builder.field("listeners", listeners);
101101
builder.endObject();
102102
return builder;

server/src/main/java/org/elasticsearch/index/reindex/BulkByScrollTask.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,12 +387,12 @@ public XContentBuilder innerXContent(XContentBuilder builder, Params params)
387387
builder.field("search", searchRetries);
388388
}
389389
builder.endObject();
390-
builder.timeValueField("throttled_millis", "throttled", throttled);
390+
builder.humanReadableField("throttled_millis", "throttled", throttled);
391391
builder.field("requests_per_second", requestsPerSecond == Float.POSITIVE_INFINITY ? -1 : requestsPerSecond);
392392
if (reasonCancelled != null) {
393393
builder.field("canceled", reasonCancelled);
394394
}
395-
builder.timeValueField("throttled_until_millis", "throttled_until", throttledUntil);
395+
builder.humanReadableField("throttled_until_millis", "throttled_until", throttledUntil);
396396
if (false == sliceStatuses.isEmpty()) {
397397
builder.startArray("slices");
398398
for (StatusOrException slice : sliceStatuses) {

0 commit comments

Comments
 (0)