Skip to content

Commit 34bd50e

Browse files
authored
feat(ui): Add cluster tag (#26485)
## Description With many flavors of presto, it has become more important to be able to identify which flavor is running. As a first step, a generic "cluster tag" approach [was decided upon](#26454 (comment)) to allow this. <!---Describe your changes in detail--> ## Motivation and Context <!---Why is this change required? What problem does it solve?--> <!---If it fixes an open issue, please link to the issue here.--> Allow presto users to tag a cluster for easy identification from the UI, eg. Worker type: Java, C++, GPU ## Impact With and without a cluster tag configured: <!---Describe any public API or user-facing feature change or any performance impact--> <img width="1194" height="347" alt="Screenshot 2025-10-30 at 12 02 34 PM" src="https://github.com/user-attachments/assets/ac69a70b-0259-4dbc-b73e-68842f90ba0a" /> <img width="1219" height="390" alt="Screenshot 2025-10-30 at 12 05 42 PM" src="https://github.com/user-attachments/assets/d5f6ee4e-92eb-4a4a-b9d9-f8fc98109970" /> ## Test Plan <!---Please fill in how you tested your change--> 1. Do _not_ set the cluster tag config flag. Confirm that the UI does not display a "Tag" column. 2. Set the config flag to any string value, confirm that the UI displays this tag in the "Tag" column 3. Set the cluster tag config value to a very long value. Confirm that the UI handles this long value appropriately by wrapping the UI tag ## Contributor checklist - [ ] Please make sure your submission complies with our [contributing guide](https://github.com/prestodb/presto/blob/master/CONTRIBUTING.md), in particular [code style](https://github.com/prestodb/presto/blob/master/CONTRIBUTING.md#code-style) and [commit standards](https://github.com/prestodb/presto/blob/master/CONTRIBUTING.md#commit-standards). - [ ] PR description addresses the issue accurately and concisely. If the change is non-trivial, a GitHub Issue is referenced. - [ ] Documented new properties (with its default value), SQL syntax, functions, or other functionality. - [ ] If release notes are required, they follow the [release notes guidelines](https://github.com/prestodb/presto/wiki/Release-Notes-Guidelines). - [ ] Adequate tests were added if applicable. - [ ] CI passed. - [ ] If adding new dependencies, verified they have an [OpenSSF Scorecard](https://securityscorecards.dev/#the-checks) score of 5.0 or higher (or obtained explicit TSC approval for lower scores). ## Release Notes Please follow [release notes guidelines](https://github.com/prestodb/presto/wiki/Release-Notes-Guidelines) and fill in the release notes below. ``` == RELEASE NOTES == General Changes * Add a configurable `clusterTag` config flag, which is returned from the `/v1/cluster` endpoints and displayed in the UI.
1 parent 02d78e8 commit 34bd50e

File tree

9 files changed

+138
-38
lines changed

9 files changed

+138
-38
lines changed

presto-docs/src/main/sphinx/admin/properties.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ If it’s below the limit, the generated prefixes are used.
172172

173173
The corresponding session property is :ref:`admin/properties-session:\`\`max_prefixes_count\`\``.
174174

175+
``cluster-tag``
176+
^^^^^^^^^^^^^^^
177+
178+
* **Type:** ``string``
179+
* **Default value:** (none)
180+
181+
An optional identifier for the cluster. When set, this tag is included in the response from the
182+
``/v1/cluster`` REST API endpoint, allowing clients to identify which cluster provided the response.
183+
175184
Memory Management Properties
176185
----------------------------
177186

presto-main-base/src/main/java/com/facebook/presto/server/ServerConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class ServerConfig
4242
private Duration clusterStatsExpirationDuration = new Duration(0, MILLISECONDS);
4343
private boolean nestedDataSerializationEnabled = true;
4444
private Duration clusterResourceGroupStateInfoExpirationDuration = new Duration(0, MILLISECONDS);
45+
private String clusterTag;
4546

4647
public boolean isResourceManager()
4748
{
@@ -240,4 +241,16 @@ public ServerConfig setClusterResourceGroupStateInfoExpirationDuration(Duration
240241
this.clusterResourceGroupStateInfoExpirationDuration = clusterResourceGroupStateInfoExpirationDuration;
241242
return this;
242243
}
244+
245+
public String getClusterTag()
246+
{
247+
return clusterTag;
248+
}
249+
250+
@Config("cluster-tag")
251+
public ServerConfig setClusterTag(String clusterTag)
252+
{
253+
this.clusterTag = clusterTag;
254+
return this;
255+
}
243256
}

presto-main-base/src/test/java/com/facebook/presto/server/TestServerConfig.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public void testDefaults()
4949
.setPoolType(DEFAULT)
5050
.setClusterStatsExpirationDuration(new Duration(0, MILLISECONDS))
5151
.setNestedDataSerializationEnabled(true)
52-
.setClusterResourceGroupStateInfoExpirationDuration(new Duration(0, MILLISECONDS)));
52+
.setClusterResourceGroupStateInfoExpirationDuration(new Duration(0, MILLISECONDS))
53+
.setClusterTag(null));
5354
}
5455

5556
@Test
@@ -72,6 +73,7 @@ public void testExplicitPropertyMappings()
7273
.put("cluster-stats-expiration-duration", "10s")
7374
.put("nested-data-serialization-enabled", "false")
7475
.put("cluster-resource-group-state-info-expiration-duration", "10s")
76+
.put("cluster-tag", "test-cluster")
7577
.build();
7678

7779
ServerConfig expected = new ServerConfig()
@@ -90,7 +92,8 @@ public void testExplicitPropertyMappings()
9092
.setPoolType(LEAF)
9193
.setClusterStatsExpirationDuration(new Duration(10, SECONDS))
9294
.setNestedDataSerializationEnabled(false)
93-
.setClusterResourceGroupStateInfoExpirationDuration(new Duration(10, SECONDS));
95+
.setClusterResourceGroupStateInfoExpirationDuration(new Duration(10, SECONDS))
96+
.setClusterTag("test-cluster");
9497

9598
assertFullMapping(properties, expected);
9699
}

presto-main/src/main/java/com/facebook/presto/resourcemanager/DistributedClusterStatsResource.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public class DistributedClusterStatsResource
5050
private final ResourceManagerClusterStateProvider clusterStateProvider;
5151
private final InternalNodeManager internalNodeManager;
5252
private final Supplier<ClusterStats> clusterStatsSupplier;
53+
private final String clusterTag;
5354

5455
@Inject
5556
public DistributedClusterStatsResource(
@@ -61,7 +62,9 @@ public DistributedClusterStatsResource(
6162
this.isIncludeCoordinator = requireNonNull(nodeSchedulerConfig, "nodeSchedulerConfig is null").isIncludeCoordinator();
6263
this.clusterStateProvider = requireNonNull(clusterStateProvider, "nodeStateManager is null");
6364
this.internalNodeManager = requireNonNull(internalNodeManager, "internalNodeManager is null");
64-
Duration expirationDuration = requireNonNull(serverConfig, "serverConfig is null").getClusterStatsExpirationDuration();
65+
ServerConfig config = requireNonNull(serverConfig, "serverConfig is null");
66+
this.clusterTag = config.getClusterTag();
67+
Duration expirationDuration = config.getClusterStatsExpirationDuration();
6568
this.clusterStatsSupplier = expirationDuration.getValue() > 0 ? memoizeWithExpiration(this::calculateClusterStats, expirationDuration.toMillis(), MILLISECONDS) : this::calculateClusterStats;
6669
}
6770

@@ -126,7 +129,8 @@ else if (query.getState() == QueryState.RUNNING) {
126129
totalInputRows,
127130
totalInputBytes,
128131
totalCpuTimeSecs,
129-
clusterStateProvider.getAdjustedQueueSize());
132+
clusterStateProvider.getAdjustedQueueSize(),
133+
clusterTag);
130134
}
131135

132136
@GET

presto-main/src/main/java/com/facebook/presto/server/ClusterStatsResource.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public class ClusterStatsResource
7676
private final InternalResourceGroupManager internalResourceGroupManager;
7777
private final ClusterTtlProviderManager clusterTtlProviderManager;
7878
private final Supplier<ClusterStats> clusterStatsSupplier;
79+
private final String clusterTag;
7980

8081
@Inject
8182
public ClusterStatsResource(
@@ -96,7 +97,9 @@ public ClusterStatsResource(
9697
this.proxyHelper = requireNonNull(proxyHelper, "internalNodeManager is null");
9798
this.internalResourceGroupManager = requireNonNull(internalResourceGroupManager, "internalResourceGroupManager is null");
9899
this.clusterTtlProviderManager = requireNonNull(clusterTtlProviderManager, "clusterTtlProvider is null");
99-
Duration expirationDuration = requireNonNull(serverConfig, "serverConfig is null").getClusterStatsExpirationDuration();
100+
ServerConfig config = requireNonNull(serverConfig, "serverConfig is null");
101+
this.clusterTag = config.getClusterTag();
102+
Duration expirationDuration = config.getClusterStatsExpirationDuration();
100103
this.clusterStatsSupplier = expirationDuration.getValue() > 0 ? memoizeWithExpiration(this::calculateClusterStats, expirationDuration.toMillis(), MILLISECONDS) : this::calculateClusterStats;
101104
}
102105

@@ -170,7 +173,8 @@ else if (query.getState() == QueryState.RUNNING) {
170173
totalInputRows,
171174
totalInputBytes,
172175
totalCpuTimeSecs,
173-
internalResourceGroupManager.getQueriesQueuedOnInternal());
176+
internalResourceGroupManager.getQueriesQueuedOnInternal(),
177+
clusterTag);
174178
}
175179

176180
@GET
@@ -238,6 +242,8 @@ public static class ClusterStats
238242
private final long totalCpuTimeSecs;
239243
private final long adjustedQueueSize;
240244

245+
private final String clusterTag;
246+
241247
@JsonCreator
242248
@ThriftConstructor
243249
public ClusterStats(
@@ -251,7 +257,8 @@ public ClusterStats(
251257
@JsonProperty("totalInputRows") long totalInputRows,
252258
@JsonProperty("totalInputBytes") long totalInputBytes,
253259
@JsonProperty("totalCpuTimeSecs") long totalCpuTimeSecs,
254-
@JsonProperty("adjustedQueueSize") long adjustedQueueSize)
260+
@JsonProperty("adjustedQueueSize") long adjustedQueueSize,
261+
@JsonProperty("clusterTag") String clusterTag)
255262
{
256263
this.runningQueries = runningQueries;
257264
this.blockedQueries = blockedQueries;
@@ -264,6 +271,7 @@ public ClusterStats(
264271
this.totalInputBytes = totalInputBytes;
265272
this.totalCpuTimeSecs = totalCpuTimeSecs;
266273
this.adjustedQueueSize = adjustedQueueSize;
274+
this.clusterTag = clusterTag;
267275
}
268276

269277
@JsonProperty
@@ -342,5 +350,12 @@ public long getAdjustedQueueSize()
342350
{
343351
return adjustedQueueSize;
344352
}
353+
354+
@JsonProperty
355+
@ThriftField(12)
356+
public String getClusterTag()
357+
{
358+
return clusterTag;
359+
}
345360
}
346361
}

presto-main/src/main/java/com/facebook/presto/server/ServerMainModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ else if (serverConfig.isCoordinator()) {
338338

339339
install(new InternalCommunicationModule());
340340

341+
configBinder(binder).bindConfig(ServerConfig.class);
341342
configBinder(binder).bindConfig(FeaturesConfig.class);
342343
configBinder(binder).bindConfig(FunctionsConfig.class);
343344
configBinder(binder).bindConfig(JavaFeaturesConfig.class);

presto-main/src/test/java/com/facebook/presto/server/TestThriftClusterStats.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class TestThriftClusterStats
4646
public static final long TOTAL_INPUT_BYTES = 1003;
4747
public static final long TOTAL_CPU_TIME_SECS = 1004;
4848
public static final long ADJUSTED_QUEUE_SIZE = 1005;
49+
public static final String CLUSTER_TAG = "test-cluster";
4950
private static final ThriftCodecManager COMPILER_READ_CODEC_MANAGER = new ThriftCodecManager(new CompilerThriftCodecFactory(false));
5051
private static final ThriftCodecManager COMPILER_WRITE_CODEC_MANAGER = new ThriftCodecManager(new CompilerThriftCodecFactory(false));
5152
private static final ThriftCodec<ClusterStats> COMPILER_READ_CODEC = COMPILER_READ_CODEC_MANAGER.getCodec(ClusterStats.class);
@@ -111,6 +112,7 @@ private void assertSerde(ClusterStats clusterStats)
111112
assertEquals(clusterStats.getTotalInputBytes(), TOTAL_INPUT_BYTES);
112113
assertEquals(clusterStats.getTotalCpuTimeSecs(), TOTAL_CPU_TIME_SECS);
113114
assertEquals(clusterStats.getAdjustedQueueSize(), ADJUSTED_QUEUE_SIZE);
115+
assertEquals(clusterStats.getClusterTag(), CLUSTER_TAG);
114116
}
115117

116118
private ClusterStats getRoundTripSerialize(ThriftCodec<ClusterStats> readCodec, ThriftCodec<ClusterStats> writeCodec, Function<TTransport, TProtocol> protocolFactory)
@@ -134,6 +136,7 @@ private ClusterStats getClusterStats()
134136
TOTAL_INPUT_ROWS,
135137
TOTAL_INPUT_BYTES,
136138
TOTAL_CPU_TIME_SECS,
137-
ADJUSTED_QUEUE_SIZE);
139+
ADJUSTED_QUEUE_SIZE,
140+
CLUSTER_TAG);
138141
}
139142
}

presto-ui/src/components/PageTitle.tsx

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export const PageTitle = (props: Props): React.ReactElement => {
5858
modalShown: false,
5959
errorText: null,
6060
});
61+
const [clusterTag, setClusterTag] = useState(null);
6162

6263
const timeoutId = useRef<number | null>(null);
6364

@@ -118,6 +119,17 @@ export const PageTitle = (props: Props): React.ReactElement => {
118119
};
119120
}, []);
120121

122+
useEffect(() => {
123+
fetch("/v1/cluster")
124+
.then((response) => response.json())
125+
.then((clusterResponse) => {
126+
setClusterTag(clusterResponse.clusterTag);
127+
})
128+
.catch((error) => {
129+
console.error("Could not fetch cluster response:", error);
130+
});
131+
}, []);
132+
121133
const renderStatusLight = () => {
122134
if (state.noConnection) {
123135
if (state.lightShown) {
@@ -138,7 +150,7 @@ export const PageTitle = (props: Props): React.ReactElement => {
138150
return (
139151
<div>
140152
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
141-
<div className="container-fluid">
153+
<div className="container-fluid gap-4">
142154
<div className="navbar-header">
143155
<table>
144156
<tbody>
@@ -168,39 +180,61 @@ export const PageTitle = (props: Props): React.ReactElement => {
168180
>
169181
<span className="navbar-toggler-icon"></span>
170182
</button>
171-
<div id="navbar" className="navbar-collapse collapse">
172-
<ul className="nav navbar-nav navbar-right ms-auto">
173-
<li>
174-
<span className="navbar-cluster-info">
175-
<span className="uppercase">Version</span>
176-
<br />
177-
<span className="text" id="version-number">
183+
<div id="navbar" className="navbar-collapse collapse min-width-0">
184+
<ul className="nav navbar-nav navbar-right gap-3 flex-nowrap justify-content-end align-items-center min-width-0 flex-grow-1">
185+
<li className="flex-basis-40 min-width-0 flex-grow-1">
186+
<div className="navbar-cluster-info">
187+
<div className="uppercase">Version</div>
188+
<div
189+
title={info?.nodeVersion?.version}
190+
className="text text-truncate"
191+
id="version-number"
192+
>
178193
{isOffline() ? "N/A" : info?.nodeVersion?.version}
179-
</span>
180-
</span>
194+
</div>
195+
</div>
181196
</li>
182-
<li>
183-
<span className="navbar-cluster-info">
184-
<span className="uppercase">Environment</span>
185-
<br />
186-
<span className="text" id="environment">
197+
<li className="flex-basis-20 min-width-0 flex-shrink-0">
198+
<div className="navbar-cluster-info">
199+
<div className="uppercase">Environment</div>
200+
<div className="text" id="environment">
187201
{isOffline() ? "N/A" : info?.environment}
188-
</span>
189-
</span>
202+
</div>
203+
</div>
190204
</li>
191-
<li>
192-
<span className="navbar-cluster-info">
193-
<span className="uppercase">Uptime</span>
194-
<br />
195-
<span data-bs-toggle="tooltip" data-bs-placement="bottom" title="Connection status">
196-
{renderStatusLight()}
197-
</span>
198-
&nbsp;
199-
<span className="text" id="uptime">
200-
{isOffline() ? "Offline" : info?.uptime}
201-
</span>
202-
</span>
205+
<li className="flex-basis-20 min-width-0 flex-shrink-0">
206+
<div className="navbar-cluster-info">
207+
<div className="uppercase">Uptime</div>
208+
<div>
209+
<span
210+
data-bs-toggle="tooltip"
211+
data-bs-placement="bottom"
212+
title="Connection status"
213+
>
214+
{renderStatusLight()}
215+
</span>
216+
&nbsp;
217+
<span className="text" id="uptime">
218+
{isOffline() ? "Offline" : info?.uptime}
219+
</span>
220+
</div>
221+
</div>
203222
</li>
223+
{clusterTag && (
224+
<li key="cluster-tag" className="min-width-0 flex-shrink-0">
225+
<div className="navbar-cluster-info">
226+
<div className="uppercase">Tag</div>
227+
<div className="text" title="Cluster Tag">
228+
<span
229+
title={clusterTag}
230+
className="badge bg-secondary truncated-badge d-inline-block"
231+
>
232+
{clusterTag}
233+
</span>
234+
</div>
235+
</div>
236+
</li>
237+
)}
204238
</ul>
205239
</div>
206240
</div>

presto-ui/src/static/assets/presto.css

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ pre {
260260
.navbar-cluster-info {
261261
display: block;
262262
box-sizing: border-box;
263-
padding: 15px;
264263
position: relative;
264+
padding: 15px 0;
265265
color: #666;
266266
font-size: 11px;
267267
}
@@ -271,6 +271,24 @@ pre {
271271
font-size: 16px;
272272
}
273273

274+
.min-width-0 {
275+
min-width: 0;
276+
}
277+
278+
.flex-basis-40 {
279+
flex-basis: 40%;
280+
min-width: 0;
281+
}
282+
283+
.flex-basis-20 {
284+
flex-basis: 20%;
285+
min-width: 0;
286+
}
287+
288+
.truncated-badge {
289+
white-space: unset;
290+
}
291+
274292
#page-subtitle {
275293
color: #8c8c8c;
276294
padding-left: 3px;

0 commit comments

Comments
 (0)