diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-02-01 19:32:58 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2021-02-02 11:11:37 +0100 |
commit | 34d699e6f5dabd5618f137fd9e76d739605c832a (patch) | |
tree | f82b809e567fe9d1c7ac06c74b59bc0d75574d5b /docker-api/src | |
parent | f9f830402992f3c56334879c891820181e6a43fe (diff) |
Decouple ContainerStats from docker-java
Diffstat (limited to 'docker-api/src')
-rw-r--r-- | docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/ContainerStats.java | 193 | ||||
-rw-r--r-- | docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerEngine.java | 42 |
2 files changed, 180 insertions, 55 deletions
diff --git a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/ContainerStats.java b/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/ContainerStats.java index 797dffdef1f..517c1f4d138 100644 --- a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/ContainerStats.java +++ b/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/ContainerStats.java @@ -1,40 +1,25 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.dockerapi; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.dockerjava.api.model.CpuStatsConfig; -import com.github.dockerjava.api.model.MemoryStatsConfig; -import com.github.dockerjava.api.model.StatisticNetworksConfig; -import com.github.dockerjava.api.model.Statistics; - -import java.io.IOException; -import java.io.UncheckedIOException; import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import java.util.stream.Collectors; +import java.util.Objects; /** - * Wrapper class for {@link com.github.dockerjava.api.model.Statistics} to prevent leaking from docker-java library. + * CPU, memory and network statistics collected from a container. * * @author freva */ +// TODO: Move this to node-admin when docker-api module can be removed public class ContainerStats { + private final Map<String, NetworkStats> networkStatsByInterface; private final MemoryStats memoryStats; private final CpuStats cpuStats; - ContainerStats(Statistics statistics) { - // Network stats are null when container uses host network - this.networkStatsByInterface = Optional.ofNullable(statistics.getNetworks()).orElseGet(Map::of) - .entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - e -> new NetworkStats(e.getValue()), - (u, v) -> { throw new IllegalStateException(); }, - TreeMap::new)); - this.memoryStats = new MemoryStats(statistics.getMemoryStats()); - this.cpuStats = new CpuStats(statistics.getCpuStats()); + public ContainerStats(Map<String, NetworkStats> networkStatsByInterface, MemoryStats memoryStats, CpuStats cpuStats) { + this.networkStatsByInterface = Map.copyOf(Objects.requireNonNull(networkStatsByInterface)); + this.memoryStats = Objects.requireNonNull(memoryStats); + this.cpuStats = Objects.requireNonNull(cpuStats); } public Map<String, NetworkStats> getNetworks() { @@ -49,7 +34,22 @@ public class ContainerStats { return cpuStats; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ContainerStats that = (ContainerStats) o; + return networkStatsByInterface.equals(that.networkStatsByInterface) && memoryStats.equals(that.memoryStats) && cpuStats.equals(that.cpuStats); + } + + @Override + public int hashCode() { + return Objects.hash(networkStatsByInterface, memoryStats, cpuStats); + } + + /** Statistics for network usage */ public static class NetworkStats { + private final long rxBytes; private final long rxDropped; private final long rxErrors; @@ -57,40 +57,109 @@ public class ContainerStats { private final long txDropped; private final long txErrors; - private NetworkStats(StatisticNetworksConfig statisticNetworksConfig) { - this.rxBytes = statisticNetworksConfig.getRxBytes(); - this.rxDropped = statisticNetworksConfig.getRxDropped(); - this.rxErrors = statisticNetworksConfig.getRxErrors(); - this.txBytes = statisticNetworksConfig.getTxBytes(); - this.txDropped = statisticNetworksConfig.getTxDropped(); - this.txErrors = statisticNetworksConfig.getTxErrors(); + public NetworkStats(long rxBytes, long rxDropped, long rxErrors, long txBytes, long txDropped, long txErrors) { + this.rxBytes = rxBytes; + this.rxDropped = rxDropped; + this.rxErrors = rxErrors; + this.txBytes = txBytes; + this.txDropped = txDropped; + this.txErrors = txErrors; } + /** Returns received bytes */ public long getRxBytes() { return this.rxBytes; } + + /** Returns received bytes, which was dropped */ public long getRxDropped() { return this.rxDropped; } + + /** Returns received errors */ public long getRxErrors() { return this.rxErrors; } + + /** Returns transmitted bytes */ public long getTxBytes() { return this.txBytes; } + + /** Returns transmitted bytes, which was dropped */ public long getTxDropped() { return this.txDropped; } + + /** Returns transmission errors */ public long getTxErrors() { return this.txErrors; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NetworkStats that = (NetworkStats) o; + return rxBytes == that.rxBytes && rxDropped == that.rxDropped && rxErrors == that.rxErrors && txBytes == that.txBytes && txDropped == that.txDropped && txErrors == that.txErrors; + } + + @Override + public int hashCode() { + return Objects.hash(rxBytes, rxDropped, rxErrors, txBytes, txDropped, txErrors); + } + + @Override + public String toString() { + return "NetworkStats{" + + "rxBytes=" + rxBytes + + ", rxDropped=" + rxDropped + + ", rxErrors=" + rxErrors + + ", txBytes=" + txBytes + + ", txDropped=" + txDropped + + ", txErrors=" + txErrors + + '}'; + } + } - public class MemoryStats { + /** Statistics for memory usage */ + public static class MemoryStats { + private final long cache; private final long usage; private final long limit; - private MemoryStats(MemoryStatsConfig memoryStats) { - this.cache = memoryStats.getStats().getCache(); - this.usage = memoryStats.getUsage(); - this.limit = memoryStats.getLimit(); + public MemoryStats(long cache, long usage, long limit) { + this.cache = cache; + this.usage = usage; + this.limit = limit; } + /** Returns memory used by cache in bytes */ public long getCache() { return this.cache; } + + /** Returns memory usage in bytes */ public long getUsage() { return this.usage; } + + /** Returns memory limit in bytes */ public long getLimit() { return this.limit; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MemoryStats that = (MemoryStats) o; + return cache == that.cache && usage == that.usage && limit == that.limit; + } + + @Override + public int hashCode() { + return Objects.hash(cache, usage, limit); + } + + @Override + public String toString() { + return "MemoryStats{" + + "cache=" + cache + + ", usage=" + usage + + ", limit=" + limit + + '}'; + } + } - public class CpuStats { + /** Statistics for CPU usage */ + public static class CpuStats { + private final int onlineCpus; private final long systemCpuUsage; private final long totalUsage; @@ -99,15 +168,15 @@ public class ContainerStats { private final long throttlingActivePeriods; private final long throttledPeriods; - public CpuStats(CpuStatsConfig cpuStats) { - // Added in 1.27 - this.onlineCpus = cpuStats.getCpuUsage().getPercpuUsage().size(); - this.systemCpuUsage = cpuStats.getSystemCpuUsage(); - this.totalUsage = cpuStats.getCpuUsage().getTotalUsage(); - this.usageInKernelMode = cpuStats.getCpuUsage().getUsageInKernelmode(); - this.throttledTime = cpuStats.getThrottlingData().getThrottledTime(); - this.throttlingActivePeriods = cpuStats.getThrottlingData().getPeriods(); - this.throttledPeriods = cpuStats.getThrottlingData().getThrottledPeriods(); + public CpuStats(int onlineCpus, long systemCpuUsage, long totalUsage, long usageInKernelMode, + long throttledTime, long throttlingActivePeriods, long throttledPeriods) { + this.onlineCpus = onlineCpus; + this.systemCpuUsage = systemCpuUsage; + this.totalUsage = totalUsage; + this.usageInKernelMode = usageInKernelMode; + this.throttledTime = throttledTime; + this.throttlingActivePeriods = throttlingActivePeriods; + this.throttledPeriods = throttledPeriods; } public int getOnlineCpus() { return this.onlineCpus; } @@ -129,15 +198,33 @@ public class ContainerStats { /** Number of periods this container hit the throttling limit */ public long getThrottledPeriods() { return throttledPeriods; } - } - // For testing only, create ContainerStats from JSON returned by docker daemon stats API - public static ContainerStats fromJson(String json) { - try { - Statistics statistics = new ObjectMapper().readValue(json, Statistics.class); - return new ContainerStats(statistics); - } catch (IOException e) { - throw new UncheckedIOException(e); + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CpuStats cpuStats = (CpuStats) o; + return onlineCpus == cpuStats.onlineCpus && systemCpuUsage == cpuStats.systemCpuUsage && totalUsage == cpuStats.totalUsage && usageInKernelMode == cpuStats.usageInKernelMode && throttledTime == cpuStats.throttledTime && throttlingActivePeriods == cpuStats.throttlingActivePeriods && throttledPeriods == cpuStats.throttledPeriods; + } + + @Override + public int hashCode() { + return Objects.hash(onlineCpus, systemCpuUsage, totalUsage, usageInKernelMode, throttledTime, throttlingActivePeriods, throttledPeriods); } + + @Override + public String toString() { + return "CpuStats{" + + "onlineCpus=" + onlineCpus + + ", systemCpuUsage=" + systemCpuUsage + + ", totalUsage=" + totalUsage + + ", usageInKernelMode=" + usageInKernelMode + + ", throttledTime=" + throttledTime + + ", throttlingActivePeriods=" + throttlingActivePeriods + + ", throttledPeriods=" + throttledPeriods + + '}'; + } + } + } diff --git a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerEngine.java b/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerEngine.java index a45855764ed..630efb7990f 100644 --- a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerEngine.java +++ b/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerEngine.java @@ -1,6 +1,7 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.dockerapi; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.ExecCreateCmdResponse; import com.github.dockerjava.api.command.InspectContainerResponse; @@ -33,6 +34,8 @@ import com.yahoo.vespa.hosted.dockerapi.metrics.Gauge; import com.yahoo.vespa.hosted.dockerapi.metrics.Metrics; import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -43,6 +46,7 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalLong; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -198,8 +202,7 @@ public class DockerEngine implements ContainerEngine { try { DockerStatsCallback statsCallback = dockerClient.statsCmd(containerName.asString()).exec(new DockerStatsCallback()); statsCallback.awaitCompletion(5, TimeUnit.SECONDS); - - return statsCallback.stats.map(ContainerStats::new); + return statsCallback.stats.map(DockerEngine::containerStatsFrom); } catch (NotFoundException ignored) { return Optional.empty(); } catch (RuntimeException | InterruptedException e) { @@ -437,4 +440,39 @@ public class DockerEngine implements ContainerEngine { return DockerClientImpl.getInstance(dockerClientConfig) .withDockerCmdExecFactory(dockerFactory); } + + private static ContainerStats containerStatsFrom(Statistics statistics) { + return new ContainerStats(Optional.ofNullable(statistics.getNetworks()).orElseGet(Map::of) + .entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> new ContainerStats.NetworkStats(e.getValue().getRxBytes(), e.getValue().getRxDropped(), + e.getValue().getRxErrors(), e.getValue().getTxBytes(), + e.getValue().getTxDropped(), e.getValue().getTxErrors()), + (u, v) -> { + throw new IllegalStateException(); + }, + TreeMap::new)), + new ContainerStats.MemoryStats(statistics.getMemoryStats().getStats().getCache(), + statistics.getMemoryStats().getUsage(), + statistics.getMemoryStats().getLimit()), + new ContainerStats.CpuStats(statistics.getCpuStats().getCpuUsage().getPercpuUsage().size(), + statistics.getCpuStats().getSystemCpuUsage(), + statistics.getCpuStats().getCpuUsage().getTotalUsage(), + statistics.getCpuStats().getCpuUsage().getUsageInKernelmode(), + statistics.getCpuStats().getThrottlingData().getThrottledTime(), + statistics.getCpuStats().getThrottlingData().getPeriods(), + statistics.getCpuStats().getThrottlingData().getThrottledPeriods())); + } + + // For testing only, create ContainerStats from JSON returned by docker daemon stats API + public static ContainerStats statsFromJson(String json) { + try { + Statistics statistics = new ObjectMapper().readValue(json, Statistics.class); + return containerStatsFrom(statistics); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } |