/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.monitor.fs;
import org.apache.lucene.util.Constants;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.NodeEnvironment.NodePath;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.attribute.FileStoreAttributeView;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Supplier;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.hamcrest.Matchers.not;
public class FsProbeTests extends ESTestCase {
public void testFsInfo() throws IOException {
try (NodeEnvironment env = newNodeEnvironment()) {
FsProbe probe = new FsProbe(Settings.EMPTY, env);
FsInfo stats = probe.stats(null, null);
assertNotNull(stats);
assertThat(stats.getTimestamp(), greaterThan(0L));
if (Constants.LINUX) {
assertNotNull(stats.getIoStats());
assertNotNull(stats.getIoStats().devicesStats);
for (int i = 0; i < stats.getIoStats().devicesStats.length; i++) {
final FsInfo.DeviceStats deviceStats = stats.getIoStats().devicesStats[i];
assertNotNull(deviceStats);
assertThat(deviceStats.currentReadsCompleted, greaterThanOrEqualTo(0L));
assertThat(deviceStats.previousReadsCompleted, equalTo(-1L));
assertThat(deviceStats.currentSectorsRead, greaterThanOrEqualTo(0L));
assertThat(deviceStats.previousSectorsRead, equalTo(-1L));
assertThat(deviceStats.currentWritesCompleted, greaterThanOrEqualTo(0L));
assertThat(deviceStats.previousWritesCompleted, equalTo(-1L));
assertThat(deviceStats.currentSectorsWritten, greaterThanOrEqualTo(0L));
assertThat(deviceStats.previousSectorsWritten, equalTo(-1L));
}
} else {
assertNull(stats.getIoStats());
}
FsInfo.Path total = stats.getTotal();
assertNotNull(total);
assertThat(total.total, greaterThan(0L));
assertThat(total.free, greaterThan(0L));
assertThat(total.available, greaterThan(0L));
for (FsInfo.Path path : stats) {
assertNotNull(path);
assertThat(path.getPath(), not(isEmptyOrNullString()));
assertThat(path.getMount(), not(isEmptyOrNullString()));
assertThat(path.getType(), not(isEmptyOrNullString()));
assertThat(path.total, greaterThan(0L));
assertThat(path.free, greaterThan(0L));
assertThat(path.available, greaterThan(0L));
}
}
}
public void testFsInfoOverflow() throws Exception {
final FsInfo.Path pathStats =
new FsInfo.Path(
"/foo/bar",
null,
randomNonNegativeLong(),
randomNonNegativeLong(),
randomNonNegativeLong());
addUntilOverflow(
pathStats,
p -> p.total,
"total",
() -> new FsInfo.Path("/foo/baz", null, randomNonNegativeLong(), 0, 0));
addUntilOverflow(
pathStats,
p -> p.free,
"free",
() -> new FsInfo.Path("/foo/baz", null, 0, randomNonNegativeLong(), 0));
addUntilOverflow(
pathStats,
p -> p.available,
"available",
() -> new FsInfo.Path("/foo/baz", null, 0, 0, randomNonNegativeLong()));
// even after overflowing these should not be negative
assertThat(pathStats.total, greaterThan(0L));
assertThat(pathStats.free, greaterThan(0L));
assertThat(pathStats.available, greaterThan(0L));
}
private void addUntilOverflow(
final FsInfo.Path pathStats,
final Function<FsInfo.Path, Long> getter,
final String field,
final Supplier<FsInfo.Path> supplier) {
FsInfo.Path pathToAdd = supplier.get();
while ((getter.apply(pathStats) + getter.apply(pathToAdd)) > 0) {
// add a path to increase the total bytes until it overflows
logger.info(
"--> adding {} bytes to {}, {} will be: {}",
getter.apply(pathToAdd),
getter.apply(pathStats),
field,
getter.apply(pathStats) + getter.apply(pathToAdd));
pathStats.add(pathToAdd);
pathToAdd = supplier.get();
}
// this overflows
logger.info(
"--> adding {} bytes to {}, {} will be: {}",
getter.apply(pathToAdd),
getter.apply(pathStats),
field,
getter.apply(pathStats) + getter.apply(pathToAdd));
assertThat(getter.apply(pathStats) + getter.apply(pathToAdd), lessThan(0L));
pathStats.add(pathToAdd);
}
public void testIoStats() {
final AtomicReference<List<String>> diskStats = new AtomicReference<>();
diskStats.set(Arrays.asList(
" 259 0 nvme0n1 336609 0 7923613 82813 10264051 0 182983933 52451441 0 2970886 52536260",
" 259 1 nvme0n1p1 602 0 9919 131 1 0 1 0 0 19 131",
" 259 2 nvme0n1p2 186 0 8626 18 24 0 60 20 0 34 38",
" 259 3 nvme0n1p3 335733 0 7901620 82658 9592875 0 182983872 50843431 0 1737726 50926087",
" 253 0 dm-0 287716 0 7184666 33457 8398869 0 118857776 18730966 0 1918440 18767169",
" 253 1 dm-1 112 0 4624 13 0 0 0 0 0 5 13",
" 253 2 dm-2 47802 0 710658 49312 1371977 0 64126096 33730596 0 1058193 33781827"));
final FsProbe probe = new FsProbe(Settings.EMPTY, null) {
@Override
List<String> readProcDiskStats() throws IOException {
return diskStats.get();
}
};
final Set<Tuple<Integer, Integer>> devicesNumbers = new HashSet<>();
devicesNumbers.add(Tuple.tuple(253, 0));
devicesNumbers.add(Tuple.tuple(253, 2));
final FsInfo.IoStats first = probe.ioStats(devicesNumbers, null);
assertNotNull(first);
assertThat(first.devicesStats[0].majorDeviceNumber, equalTo(253));
assertThat(first.devicesStats[0].minorDeviceNumber, equalTo(0));
assertThat(first.devicesStats[0].deviceName, equalTo("dm-0"));
assertThat(first.devicesStats[0].currentReadsCompleted, equalTo(287716L));
assertThat(first.devicesStats[0].previousReadsCompleted, equalTo(-1L));
assertThat(first.devicesStats[0].currentSectorsRead, equalTo(7184666L));
assertThat(first.devicesStats[0].previousSectorsRead, equalTo(-1L));
assertThat(first.devicesStats[0].currentWritesCompleted, equalTo(8398869L));
assertThat(first.devicesStats[0].previousWritesCompleted, equalTo(-1L));
assertThat(first.devicesStats[0].currentSectorsWritten, equalTo(118857776L));
assertThat(first.devicesStats[0].previousSectorsWritten, equalTo(-1L));
assertThat(first.devicesStats[1].majorDeviceNumber, equalTo(253));
assertThat(first.devicesStats[1].minorDeviceNumber, equalTo(2));
assertThat(first.devicesStats[1].deviceName, equalTo("dm-2"));
assertThat(first.devicesStats[1].currentReadsCompleted, equalTo(47802L));
assertThat(first.devicesStats[1].previousReadsCompleted, equalTo(-1L));
assertThat(first.devicesStats[1].currentSectorsRead, equalTo(710658L));
assertThat(first.devicesStats[1].previousSectorsRead, equalTo(-1L));
assertThat(first.devicesStats[1].currentWritesCompleted, equalTo(1371977L));
assertThat(first.devicesStats[1].previousWritesCompleted, equalTo(-1L));
assertThat(first.devicesStats[1].currentSectorsWritten, equalTo(64126096L));
assertThat(first.devicesStats[1].previousSectorsWritten, equalTo(-1L));
diskStats.set(Arrays.asList(
" 259 0 nvme0n1 336870 0 7928397 82876 10264393 0 182986405 52451610 0 2971042 52536492",
" 259 1 nvme0n1p1 602 0 9919 131 1 0 1 0 0 19 131",
" 259 2 nvme0n1p2 186 0 8626 18 24 0 60 20 0 34 38",
" 259 3 nvme0n1p3 335994 0 7906404 82721 9593184 0 182986344 50843529 0 1737840 50926248",
" 253 0 dm-0 287734 0 7185242 33464 8398869 0 118857776 18730966 0 1918444 18767176",
" 253 1 dm-1 112 0 4624 13 0 0 0 0 0 5 13",
" 253 2 dm-2 48045 0 714866 49369 1372291 0 64128568 33730766 0 1058347 33782056"));
final FsInfo previous = new FsInfo(System.currentTimeMillis(), first, new FsInfo.Path[0]);
final FsInfo.IoStats second = probe.ioStats(devicesNumbers, previous);
assertNotNull(second);
assertThat(second.devicesStats[0].majorDeviceNumber, equalTo(253));
assertThat(second.devicesStats[0].minorDeviceNumber, equalTo(0));
assertThat(second.devicesStats[0].deviceName, equalTo("dm-0"));
assertThat(second.devicesStats[0].currentReadsCompleted, equalTo(287734L));
assertThat(second.devicesStats[0].previousReadsCompleted, equalTo(287716L));
assertThat(second.devicesStats[0].currentSectorsRead, equalTo(7185242L));
assertThat(second.devicesStats[0].previousSectorsRead, equalTo(7184666L));
assertThat(second.devicesStats[0].currentWritesCompleted, equalTo(8398869L));
assertThat(second.devicesStats[0].previousWritesCompleted, equalTo(8398869L));
assertThat(second.devicesStats[0].currentSectorsWritten, equalTo(118857776L));
assertThat(second.devicesStats[0].previousSectorsWritten, equalTo(118857776L));
assertThat(second.devicesStats[1].majorDeviceNumber, equalTo(253));
assertThat(second.devicesStats[1].minorDeviceNumber, equalTo(2));
assertThat(second.devicesStats[1].deviceName, equalTo("dm-2"));
assertThat(second.devicesStats[1].currentReadsCompleted, equalTo(48045L));
assertThat(second.devicesStats[1].previousReadsCompleted, equalTo(47802L));
assertThat(second.devicesStats[1].currentSectorsRead, equalTo(714866L));
assertThat(second.devicesStats[1].previousSectorsRead, equalTo(710658L));
assertThat(second.devicesStats[1].currentWritesCompleted, equalTo(1372291L));
assertThat(second.devicesStats[1].previousWritesCompleted, equalTo(1371977L));
assertThat(second.devicesStats[1].currentSectorsWritten, equalTo(64128568L));
assertThat(second.devicesStats[1].previousSectorsWritten, equalTo(64126096L));
assertThat(second.totalOperations, equalTo(575L));
assertThat(second.totalReadOperations, equalTo(261L));
assertThat(second.totalWriteOperations, equalTo(314L));
assertThat(second.totalReadKilobytes, equalTo(2392L));
assertThat(second.totalWriteKilobytes, equalTo(1236L));
}
public void testAdjustForHugeFilesystems() throws Exception {
NodePath np = new FakeNodePath(createTempDir());
assertThat(FsProbe.getFSInfo(np).total, greaterThanOrEqualTo(0L));
}
static class FakeNodePath extends NodeEnvironment.NodePath {
public final FileStore fileStore;
FakeNodePath(Path path) throws IOException {
super(path);
this.fileStore = new HugeFileStore();
}
}
/**
* Randomly returns negative values for disk space to simulate https://bugs.openjdk.java.net/browse/JDK-8162520
*/
static class HugeFileStore extends FileStore {
@Override
public String name() {
return "myHugeFS";
}
@Override
public String type() {
return "bigFS";
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public long getTotalSpace() throws IOException {
return randomIntBetween(-1000, 1000);
}
@Override
public long getUsableSpace() throws IOException {
return 10;
}
@Override
public long getUnallocatedSpace() throws IOException {
return 10;
}
@Override
public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
return false;
}
@Override
public boolean supportsFileAttributeView(String name) {
return false;
}
@Override
public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
throw new UnsupportedOperationException("don't call me");
}
@Override
public Object getAttribute(String attribute) throws IOException {
throw new UnsupportedOperationException("don't call me");
}
}
}