Skip to content

Commit f912948

Browse files
committed
Add more detailed OS name on Linux (#35352)
Today our OS information returned in node stats only returns a high-level name of the OS (e.g., "Linux"). Yet, for some uses this is too high-level and knowing at a finer level of granularity the underlying OS can be useful. This commit extracts the pretty name on Linux from /etc/os-release. This pretty name usually includes the Linux vendor and the Linux vendor version number (e.g., Fedora 28).
1 parent bf8e4b7 commit f912948

File tree

11 files changed

+240
-54
lines changed

11 files changed

+240
-54
lines changed

docs/reference/cluster/stats.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ Will return, for example:
123123
"count": 1
124124
}
125125
],
126+
"pretty_names": [
127+
{
128+
"pretty_name": "Mac OS X",
129+
"count": 1
130+
}
131+
],
126132
"mem" : {
127133
"total" : "16gb",
128134
"total_in_bytes" : 17179869184,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.monitor.os;
21+
22+
import org.apache.lucene.util.Constants;
23+
import org.elasticsearch.common.io.PathUtils;
24+
import org.elasticsearch.test.ESTestCase;
25+
26+
import java.io.IOException;
27+
import java.nio.file.Files;
28+
import java.util.List;
29+
import java.util.regex.Matcher;
30+
import java.util.regex.Pattern;
31+
32+
import static org.hamcrest.Matchers.equalTo;
33+
34+
public class EvilOsProbeTests extends ESTestCase {
35+
36+
public void testOsPrettyName() throws IOException {
37+
final OsInfo osInfo = OsProbe.getInstance().osInfo(randomLongBetween(1, 100), randomIntBetween(1, 8));
38+
if (Constants.LINUX) {
39+
final List<String> lines = Files.readAllLines(PathUtils.get("/etc/os-release"));
40+
for (final String line : lines) {
41+
if (line != null && line.startsWith("PRETTY_NAME=")) {
42+
final Matcher matcher = Pattern.compile("PRETTY_NAME=(\"?|'?)?([^\"']+)\\1").matcher(line);
43+
assert matcher.matches() : line;
44+
final String prettyName = matcher.group(2);
45+
assertThat(osInfo.getPrettyName(), equalTo(prettyName));
46+
return;
47+
}
48+
}
49+
assertThat(osInfo.getPrettyName(), equalTo("Linux"));
50+
} else {
51+
assertThat(osInfo.getPrettyName(), equalTo(Constants.OS_NAME));
52+
}
53+
}
54+
55+
}

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,15 @@ public static class OsStats implements ToXContentFragment {
226226
final int availableProcessors;
227227
final int allocatedProcessors;
228228
final ObjectIntHashMap<String> names;
229+
final ObjectIntHashMap<String> prettyNames;
229230
final org.elasticsearch.monitor.os.OsStats.Mem mem;
230231

231232
/**
232233
* Build the stats from information about each node.
233234
*/
234235
private OsStats(List<NodeInfo> nodeInfos, List<NodeStats> nodeStatsList) {
235236
this.names = new ObjectIntHashMap<>();
237+
this.prettyNames = new ObjectIntHashMap<>();
236238
int availableProcessors = 0;
237239
int allocatedProcessors = 0;
238240
for (NodeInfo nodeInfo : nodeInfos) {
@@ -242,6 +244,9 @@ private OsStats(List<NodeInfo> nodeInfos, List<NodeStats> nodeStatsList) {
242244
if (nodeInfo.getOs().getName() != null) {
243245
names.addTo(nodeInfo.getOs().getName(), 1);
244246
}
247+
if (nodeInfo.getOs().getPrettyName() != null) {
248+
prettyNames.addTo(nodeInfo.getOs().getPrettyName(), 1);
249+
}
245250
}
246251
this.availableProcessors = availableProcessors;
247252
this.allocatedProcessors = allocatedProcessors;
@@ -280,6 +285,8 @@ static final class Fields {
280285
static final String ALLOCATED_PROCESSORS = "allocated_processors";
281286
static final String NAME = "name";
282287
static final String NAMES = "names";
288+
static final String PRETTY_NAME = "pretty_name";
289+
static final String PRETTY_NAMES = "pretty_names";
283290
static final String COUNT = "count";
284291
}
285292

@@ -289,11 +296,27 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params)
289296
builder.field(Fields.AVAILABLE_PROCESSORS, availableProcessors);
290297
builder.field(Fields.ALLOCATED_PROCESSORS, allocatedProcessors);
291298
builder.startArray(Fields.NAMES);
292-
for (ObjectIntCursor<String> name : names) {
293-
builder.startObject();
294-
builder.field(Fields.NAME, name.key);
295-
builder.field(Fields.COUNT, name.value);
296-
builder.endObject();
299+
{
300+
for (ObjectIntCursor<String> name : names) {
301+
builder.startObject();
302+
{
303+
builder.field(Fields.NAME, name.key);
304+
builder.field(Fields.COUNT, name.value);
305+
}
306+
builder.endObject();
307+
}
308+
}
309+
builder.endArray();
310+
builder.startArray(Fields.PRETTY_NAMES);
311+
{
312+
for (final ObjectIntCursor<String> prettyName : prettyNames) {
313+
builder.startObject();
314+
{
315+
builder.field(Fields.PRETTY_NAME, prettyName.key);
316+
builder.field(Fields.COUNT, prettyName.value);
317+
}
318+
builder.endObject();
319+
}
297320
}
298321
builder.endArray();
299322
mem.toXContent(builder, params);

server/src/main/java/org/elasticsearch/monitor/os/DummyOsInfo.java

Lines changed: 0 additions & 29 deletions
This file was deleted.

server/src/main/java/org/elasticsearch/monitor/os/OsInfo.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919

2020
package org.elasticsearch.monitor.os;
2121

22+
import org.elasticsearch.Version;
2223
import org.elasticsearch.common.io.stream.StreamInput;
2324
import org.elasticsearch.common.io.stream.StreamOutput;
2425
import org.elasticsearch.common.io.stream.Writeable;
2526
import org.elasticsearch.common.unit.TimeValue;
26-
import org.elasticsearch.common.xcontent.ToXContent.Params;
2727
import org.elasticsearch.common.xcontent.ToXContentFragment;
2828
import org.elasticsearch.common.xcontent.XContentBuilder;
2929

@@ -35,14 +35,23 @@ public class OsInfo implements Writeable, ToXContentFragment {
3535
private final int availableProcessors;
3636
private final int allocatedProcessors;
3737
private final String name;
38+
private final String prettyName;
3839
private final String arch;
3940
private final String version;
4041

41-
public OsInfo(long refreshInterval, int availableProcessors, int allocatedProcessors, String name, String arch, String version) {
42+
public OsInfo(
43+
final long refreshInterval,
44+
final int availableProcessors,
45+
final int allocatedProcessors,
46+
final String name,
47+
final String prettyName,
48+
final String arch,
49+
final String version) {
4250
this.refreshInterval = refreshInterval;
4351
this.availableProcessors = availableProcessors;
4452
this.allocatedProcessors = allocatedProcessors;
4553
this.name = name;
54+
this.prettyName = prettyName;
4655
this.arch = arch;
4756
this.version = version;
4857
}
@@ -52,6 +61,11 @@ public OsInfo(StreamInput in) throws IOException {
5261
this.availableProcessors = in.readInt();
5362
this.allocatedProcessors = in.readInt();
5463
this.name = in.readOptionalString();
64+
if (in.getVersion().onOrAfter(Version.V_6_6_0)) {
65+
this.prettyName = in.readOptionalString();
66+
} else {
67+
this.prettyName = null;
68+
}
5569
this.arch = in.readOptionalString();
5670
this.version = in.readOptionalString();
5771
}
@@ -62,6 +76,9 @@ public void writeTo(StreamOutput out) throws IOException {
6276
out.writeInt(availableProcessors);
6377
out.writeInt(allocatedProcessors);
6478
out.writeOptionalString(name);
79+
if (out.getVersion().onOrAfter(Version.V_6_6_0)) {
80+
out.writeOptionalString(prettyName);
81+
}
6582
out.writeOptionalString(arch);
6683
out.writeOptionalString(version);
6784
}
@@ -82,6 +99,10 @@ public String getName() {
8299
return name;
83100
}
84101

102+
public String getPrettyName() {
103+
return prettyName;
104+
}
105+
85106
public String getArch() {
86107
return arch;
87108
}
@@ -93,6 +114,7 @@ public String getVersion() {
93114
static final class Fields {
94115
static final String OS = "os";
95116
static final String NAME = "name";
117+
static final String PRETTY_NAME = "pretty_name";
96118
static final String ARCH = "arch";
97119
static final String VERSION = "version";
98120
static final String REFRESH_INTERVAL = "refresh_interval";
@@ -108,6 +130,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
108130
if (name != null) {
109131
builder.field(Fields.NAME, name);
110132
}
133+
if (prettyName != null) {
134+
builder.field(Fields.PRETTY_NAME, prettyName);
135+
}
111136
if (arch != null) {
112137
builder.field(Fields.ARCH, arch);
113138
}

server/src/main/java/org/elasticsearch/monitor/os/OsProbe.java

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
package org.elasticsearch.monitor.os;
2121

22-
import org.apache.logging.log4j.Logger;
2322
import org.apache.logging.log4j.LogManager;
23+
import org.apache.logging.log4j.Logger;
2424
import org.apache.lucene.util.Constants;
2525
import org.elasticsearch.common.SuppressForbidden;
2626
import org.elasticsearch.common.io.PathUtils;
@@ -36,6 +36,8 @@
3636
import java.util.HashMap;
3737
import java.util.List;
3838
import java.util.Map;
39+
import java.util.Optional;
40+
import java.util.stream.Collectors;
3941

4042
public class OsProbe {
4143

@@ -519,9 +521,68 @@ public static OsProbe getInstance() {
519521

520522
private final Logger logger = LogManager.getLogger(getClass());
521523

522-
public OsInfo osInfo(long refreshInterval, int allocatedProcessors) {
523-
return new OsInfo(refreshInterval, Runtime.getRuntime().availableProcessors(),
524-
allocatedProcessors, Constants.OS_NAME, Constants.OS_ARCH, Constants.OS_VERSION);
524+
OsInfo osInfo(long refreshInterval, int allocatedProcessors) throws IOException {
525+
return new OsInfo(
526+
refreshInterval,
527+
Runtime.getRuntime().availableProcessors(),
528+
allocatedProcessors,
529+
Constants.OS_NAME,
530+
getPrettyName(),
531+
Constants.OS_ARCH,
532+
Constants.OS_VERSION);
533+
}
534+
535+
private String getPrettyName() throws IOException {
536+
// TODO: return a prettier name on non-Linux OS
537+
if (Constants.LINUX) {
538+
/*
539+
* We read the lines from /etc/os-release (or /usr/lib/os-release) to extract the PRETTY_NAME. The format of this file is
540+
* newline-separated key-value pairs. The key and value are separated by an equals symbol (=). The value can unquoted, or
541+
* wrapped in single- or double-quotes.
542+
*/
543+
final List<String> etcOsReleaseLines = readOsRelease();
544+
final List<String> prettyNameLines =
545+
etcOsReleaseLines.stream().filter(line -> line.startsWith("PRETTY_NAME")).collect(Collectors.toList());
546+
assert prettyNameLines.size() <= 1 : prettyNameLines;
547+
final Optional<String> maybePrettyNameLine =
548+
prettyNameLines.size() == 1 ? Optional.of(prettyNameLines.get(0)) : Optional.empty();
549+
if (maybePrettyNameLine.isPresent()) {
550+
final String prettyNameLine = maybePrettyNameLine.get();
551+
final String[] prettyNameFields = prettyNameLine.split("=");
552+
assert prettyNameFields.length == 2 : prettyNameLine;
553+
if (prettyNameFields[1].length() >= 3 &&
554+
(prettyNameFields[1].startsWith("\"") && prettyNameFields[1].endsWith("\"")) ||
555+
(prettyNameFields[1].startsWith("'") && prettyNameFields[1].endsWith("'"))) {
556+
return prettyNameFields[1].substring(1, prettyNameFields[1].length() - 1);
557+
} else {
558+
return prettyNameFields[1];
559+
}
560+
} else {
561+
return Constants.OS_NAME;
562+
}
563+
564+
} else {
565+
return Constants.OS_NAME;
566+
}
567+
}
568+
569+
/**
570+
* The lines from {@code /etc/os-release} or {@code /usr/lib/os-release} as a fallback. These file represents identification of the
571+
* underlying operating system. The structure of the file is newlines of key-value pairs of shell-compatible variable assignments.
572+
*
573+
* @return the lines from {@code /etc/os-release} or {@code /usr/lib/os-release}
574+
* @throws IOException if an I/O exception occurs reading {@code /etc/os-release} or {@code /usr/lib/os-release}
575+
*/
576+
@SuppressForbidden(reason = "access /etc/os-release or /usr/lib/os-release")
577+
List<String> readOsRelease() throws IOException {
578+
final List<String> lines;
579+
if (Files.exists(PathUtils.get("/etc/os-release"))) {
580+
lines = Files.readAllLines(PathUtils.get("/etc/os-release"));
581+
} else {
582+
lines = Files.readAllLines(PathUtils.get("/usr/lib/os-release"));
583+
}
584+
assert lines != null && lines.isEmpty() == false;
585+
return lines;
525586
}
526587

527588
public OsStats osStats() {

server/src/main/java/org/elasticsearch/monitor/os/OsService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.elasticsearch.common.util.SingleObjectCache;
2828
import org.elasticsearch.common.util.concurrent.EsExecutors;
2929

30+
import java.io.IOException;
31+
3032
public class OsService extends AbstractComponent {
3133

3234
private final OsProbe probe;
@@ -37,7 +39,7 @@ public class OsService extends AbstractComponent {
3739
Setting.timeSetting("monitor.os.refresh_interval", TimeValue.timeValueSeconds(1), TimeValue.timeValueSeconds(1),
3840
Property.NodeScope);
3941

40-
public OsService(Settings settings) {
42+
public OsService(Settings settings) throws IOException {
4143
this.probe = OsProbe.getInstance();
4244
TimeValue refreshInterval = REFRESH_INTERVAL_SETTING.get(settings);
4345
this.info = probe.osInfo(refreshInterval.millis(), EsExecutors.numberOfProcessors(settings));

server/src/main/resources/org/elasticsearch/bootstrap/security.policy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ grant {
124124
// read max virtual memory areas
125125
permission java.io.FilePermission "/proc/sys/vm/max_map_count", "read";
126126

127+
// OS release on Linux
128+
permission java.io.FilePermission "/etc/os-release", "read";
129+
permission java.io.FilePermission "/usr/lib/os-release", "read";
130+
127131
// io stats on Linux
128132
permission java.io.FilePermission "/proc/self/mountinfo", "read";
129133
permission java.io.FilePermission "/proc/diskstats", "read";

0 commit comments

Comments
 (0)