NIFI-11705 Append Operating System section in NiFi diagnostic tool

This closes #7388

Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Timea Barna 2023-06-16 15:14:28 +02:00 committed by exceptionfactory
parent 5d3443c191
commit dceee57dfd
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
11 changed files with 495 additions and 1 deletions

View File

@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.command;
import org.apache.commons.lang3.SystemUtils;
import org.apache.nifi.diagnostics.bootstrap.shell.result.ShellCommandResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
public abstract class AbstractShellCommand implements PlatformShellCommand {
private static final Logger logger = LoggerFactory.getLogger(AbstractShellCommand.class);
private final String name;
private final String[] windowsCommand;
private final String[] linuxCommand;
private final String[] macCommand;
private final ShellCommandResult result;
public AbstractShellCommand(final String name, final String[] windowsCommand, final String[] linuxCommand, final String[] macCommand, final ShellCommandResult result) {
this.name = name;
this.windowsCommand = windowsCommand;
this.linuxCommand = linuxCommand;
this.macCommand = macCommand;
this.result = result;
}
public String getName() {
return name;
}
public String[] getCommand() {
if (SystemUtils.IS_OS_MAC) {
return macCommand;
} else if (SystemUtils.IS_OS_UNIX || SystemUtils.IS_OS_LINUX) {
return linuxCommand;
} else if (SystemUtils.IS_OS_WINDOWS) {
return windowsCommand;
} else {
throw new UnsupportedOperationException("Operating system not supported.");
}
}
@Override
public Collection<String> execute() {
final ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(getCommand());
try {
final Process process = processBuilder.start();
return result.createResult(process.getInputStream());
} catch (UnsupportedOperationException e) {
logger.warn(String.format("Operating system is not supported, failed to execute command: %s, ", name));
return Collections.EMPTY_LIST;
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to execute command: %s", name), e);
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.command;
import org.apache.nifi.diagnostics.bootstrap.shell.result.LineSplittingResult;
import java.util.Arrays;
import java.util.List;
public class GetDiskLayoutCommand extends AbstractShellCommand {
private static final String COMMAND_NAME = "GetDiskLayout";
private static final List<String> RESULT_LABELS = Arrays.asList("FileSystem/DeviceId", "Total size", "Used", "Free");
private static final String REGEX_FOR_SPLITTING = "\\s+";
private static final String[] GET_DISK_LAYOUT_FOR_MAC = new String[] {"/bin/sh", "-c", "df -Ph | sed 1d"};
private static final String[] GET_DISK_LAYOUT_FOR_LINUX = new String[] {"/bin/sh", "-c", "df -h --output=source,size,used,avail,target -x tmpfs -x devtmpfs | sed 1d"};
private static final String[] GET_DISK_LAYOUT_FOR_WINDOWS = new String[] {"powershell.exe", "Get-CIMInstance Win32_LogicalDisk " +
"| ft -hideTableHeaders DeviceId, @{n='Size/GB'; e={[math]::truncate($_.Size/1GB)}}," +
" @{n='Used/GB'; e={[math]::truncate($_.Size/1GB - $_.FreeSpace/1GB)}}, @{n='FreeSpace/GB'; e={[math]::truncate($_.freespace/1GB)}}, VolumeName"};
public GetDiskLayoutCommand() {
super(COMMAND_NAME,
GET_DISK_LAYOUT_FOR_WINDOWS,
GET_DISK_LAYOUT_FOR_LINUX,
GET_DISK_LAYOUT_FOR_MAC,
new LineSplittingResult(REGEX_FOR_SPLITTING, RESULT_LABELS, COMMAND_NAME)
);
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.command;
import org.apache.nifi.diagnostics.bootstrap.shell.result.SingleLineResult;
public class GetPhysicalCpuCoresCommand extends AbstractShellCommand {
private static final String COMMAND_NAME = "GetPhysicalCpuCores";
private static final String RESULT_LABEL = "Number of physical CPU core(s)";
private static final String[] GET_CPU_CORE_FOR_MAC = new String[] {"/bin/sh", "-c", "sysctl -n hw.physicalcpu"};
private static final String[] GET_CPU_CORE_FOR_LINUX = new String[] {"/bin/sh", "-c", "lscpu -b -p=Core,Socket | grep -v '^#' | sort -u | wc -l"};
private static final String[] GET_CPU_CORE_FOR_WINDOWS = new String[] {"powershell.exe", "(Get-CIMInstance Win32_processor | ft NumberOfCores -hideTableHeaders | Out-String).trim()"};
public GetPhysicalCpuCoresCommand() {
super(COMMAND_NAME,
GET_CPU_CORE_FOR_WINDOWS,
GET_CPU_CORE_FOR_LINUX,
GET_CPU_CORE_FOR_MAC,
new SingleLineResult(RESULT_LABEL, COMMAND_NAME)
);
}
}

View File

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.command;
import org.apache.nifi.diagnostics.bootstrap.shell.result.SingleLineResult;
public class GetTotalPhysicalRamCommand extends AbstractShellCommand {
private static final String COMMAND_NAME = "GetTotalPhysicalRam";
private static final String RESULT_LABEL = "Total size of physical RAM";
private static final String[] GET_TOTAL_PHYSICAL_RAM_FOR_MAC = new String[] {"/bin/sh", "-c", "sysctl -n hw.memsize"};
private static final String[] GET_TOTAL_PHYSICAL_RAM_FOR_LINUX = new String[] {"/bin/sh", "-c", "free | grep Mem | awk '{print $2}'"};
private static final String[] GET_TOTAL_PHYSICAL_RAM_FOR_WINDOWS = new String[] {"powershell.exe", "(Get-CimInstance Win32_OperatingSystem |" +
" ft -hideTableHeaders TotalVisibleMemorySize | Out-String).trim()"};
public GetTotalPhysicalRamCommand() {
super(COMMAND_NAME,
GET_TOTAL_PHYSICAL_RAM_FOR_WINDOWS,
GET_TOTAL_PHYSICAL_RAM_FOR_LINUX,
GET_TOTAL_PHYSICAL_RAM_FOR_MAC,
new SingleLineResult(RESULT_LABEL, COMMAND_NAME)
);
}
}

View File

@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.command;
import java.util.Collection;
public interface PlatformShellCommand {
Collection<String> execute();
}

View File

@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.result;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
public class LineSplittingResult implements ShellCommandResult {
private final Pattern regexForSplitting;
private final List<String> labels;
private final String commandName;
public LineSplittingResult(final String regexStringForSplitting, final List<String> labels, final String commandName) {
this.regexForSplitting = Pattern.compile(regexStringForSplitting);
this.labels = labels;
this.commandName = commandName;
}
@Override
public Collection<String> createResult(final InputStream inputStream) {
final List<String> result = new ArrayList<>();
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.isEmpty()) {
String[] splitResults = regexForSplitting.split(line);
for (int i = 0; i < labels.size(); i++) {
result.add(String.format("%s : %s", labels.get(i), splitResults[i]));
}
}
}
return result;
} catch (IOException e) {
throw new RuntimeException(String.format("Failed to process result for command: %s", commandName),e);
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.result;
import java.io.InputStream;
import java.util.Collection;
public interface ShellCommandResult {
Collection<String> createResult(final InputStream inputStream);
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.result;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class SingleLineResult implements ShellCommandResult {
private final String label;
private final String commandName;
public SingleLineResult(final String label, final String commandName) {
this.label = label;
this.commandName = commandName;
}
public Collection<String> createResult(final InputStream inputStream) {
final List<String> result = new ArrayList<>();
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.isEmpty()) {
result.add(String.format("%s : %s", label, line));
}
}
return result;
} catch (IOException e) {
throw new RuntimeException(String.format("Failed to process result for command: %s", commandName),e);
}
}
}

View File

@ -16,9 +16,12 @@
*/
package org.apache.nifi.diagnostics.bootstrap.tasks;
import org.apache.nifi.diagnostics.DiagnosticsDumpElement;
import org.apache.nifi.diagnostics.DiagnosticTask;
import org.apache.nifi.diagnostics.DiagnosticsDumpElement;
import org.apache.nifi.diagnostics.StandardDiagnosticsDumpElement;
import org.apache.nifi.diagnostics.bootstrap.shell.command.GetPhysicalCpuCoresCommand;
import org.apache.nifi.diagnostics.bootstrap.shell.command.GetDiskLayoutCommand;
import org.apache.nifi.diagnostics.bootstrap.shell.command.GetTotalPhysicalRamCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -30,6 +33,7 @@ import java.lang.management.OperatingSystemMXBean;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -69,6 +73,9 @@ public class OperatingSystemDiagnosticTask implements DiagnosticTask {
}
attributes.forEach((key, value) -> details.add(key + " : " + value));
details.addAll(getPhysicalCPUCores());
details.addAll(getTotalPhysicalRam());
details.addAll(getDiskLayout());
} catch (final Exception e) {
logger.error("Failed to obtain Operating System details", e);
return new StandardDiagnosticsDumpElement("Operating System / Hardware", Collections.singletonList("Failed to obtain Operating System details"));
@ -76,4 +83,16 @@ public class OperatingSystemDiagnosticTask implements DiagnosticTask {
return new StandardDiagnosticsDumpElement("Operating System / Hardware", details);
}
private Collection<String> getPhysicalCPUCores() {
return new GetPhysicalCpuCoresCommand().execute();
}
private Collection<String> getTotalPhysicalRam() {
return new GetTotalPhysicalRamCommand().execute();
}
private Collection<String> getDiskLayout() {
return new GetDiskLayoutCommand().execute();
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.result;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class LineSplittingResultTest {
private static final String REGEX_FOR_SPLITTING = "\\s+";
private static final List<String> RESULT_LABELS = Arrays.asList("Label1", "Label2", "Label3", "Label4");
private static final String COMMAND_NAME = "Test command";
private static final String EMPTY_RESPONSE = "";
private static final String RESPONSE = "data1 data2 data3 data4";
private static final String RESPONSE_WITH_EMPTY_LINES = "\ndata1 data2 data3 data4\n";
private static final List<String> EXPECTED_RESPONSE = Arrays.asList("Label1 : data1", "Label2 : data2", "Label3 : data3", "Label4 : data4");
private static LineSplittingResult lineSplittingResult;
@BeforeAll
public static void setUp() {
lineSplittingResult = new LineSplittingResult(REGEX_FOR_SPLITTING, RESULT_LABELS, COMMAND_NAME);
}
@Test
public void testEmptyResponse() {
final InputStream inputStream = new ByteArrayInputStream(EMPTY_RESPONSE.getBytes(StandardCharsets.UTF_8));
assertTrue(lineSplittingResult.createResult(inputStream).isEmpty());
}
@Test
public void testResponseWithoutEmptyLines() {
final InputStream inputStream = new ByteArrayInputStream(RESPONSE.getBytes(StandardCharsets.UTF_8));
assertEquals(EXPECTED_RESPONSE, lineSplittingResult.createResult(inputStream));
}
@Test
public void testResponseWithEmptyLines() {
final InputStream inputStream = new ByteArrayInputStream(RESPONSE_WITH_EMPTY_LINES.getBytes(StandardCharsets.UTF_8));
assertEquals(EXPECTED_RESPONSE, lineSplittingResult.createResult(inputStream));
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.diagnostics.bootstrap.shell.result;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SingleLineResultTest {
private static final String LABEL = "Label";
private static final String COMMAND_NAME = "Test command";
private static final String EMPTY_RESPONSE = "";
private static final String RESPONSE = "data";
private static final String RESPONSE_WITH_EMPTY_LINES = "\ndata\n";
private static final List<String> EXPECTED_RESPONSE = Arrays.asList("Label : data");
private static SingleLineResult singleLineResult;
@BeforeAll
public static void setUp() {
singleLineResult = new SingleLineResult(LABEL, COMMAND_NAME);
}
@Test
public void testEmptyResponse() {
final InputStream inputStream = new ByteArrayInputStream(EMPTY_RESPONSE.getBytes(StandardCharsets.UTF_8));
assertTrue(singleLineResult.createResult(inputStream).isEmpty());
}
@Test
public void testResponseWithoutEmptyLines() {
final InputStream inputStream = new ByteArrayInputStream(RESPONSE.getBytes(StandardCharsets.UTF_8));
assertEquals(EXPECTED_RESPONSE, singleLineResult.createResult(inputStream));
}
@Test
public void testResponseWithEmptyLines() {
final InputStream inputStream = new ByteArrayInputStream(RESPONSE_WITH_EMPTY_LINES.getBytes(StandardCharsets.UTF_8));
assertEquals(EXPECTED_RESPONSE, singleLineResult.createResult(inputStream));
}
}