summaryrefslogtreecommitdiffstats
path: root/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/ContainerResources.java
blob: 64d8fd6a1e8721efb3a80bc448b04b35946cad4a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Copyright 2018 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 java.util.Objects;

/**
 * @author valerijf
 */
public class ContainerResources {
    public static final ContainerResources UNLIMITED = ContainerResources.from(0, 0, 0);
    private static final int CPU_PERIOD = 100_000; // 100 µs

    /** Hard limit on container's CPU usage: Implemented using Completely Fair Scheduler (CFS) by allocating a given
     * time within a given period, Container's processes are not bound to any specific CPU, which may create significant
     * performance degradation as processes are scheduled on another CPU after exhausting the quota. */
    private final double cpus;

    /** Soft limit on container's CPU usage:  When plenty of CPU cycles are available, all containers use as much
     * CPU as they need. It prioritizes container CPU resources for the available CPU cycles.
     * It does not guarantee or reserve any specific CPU access. */
    private final int cpuShares;

    /** The maximum amount, in bytes, of memory the container can use. */
    private final long memoryBytes;

    ContainerResources(double cpus, int cpuShares, long memoryBytes) {
        this.cpus = cpus;
        this.cpuShares = cpuShares;
        this.memoryBytes = memoryBytes;

        if (cpus < 0)
            throw new IllegalArgumentException("CPUs must be a positive number or 0 for unlimited, was " + cpus);
        if (cpuShares < 0)
            throw new IllegalArgumentException("CPU shares must be a positive integer or 0 for unlimited, was " + cpuShares);
        if (memoryBytes < 0)
            throw new IllegalArgumentException("memoryBytes must be a positive integer or 0 for unlimited, was " + memoryBytes);
    }

    public static ContainerResources from(double cpus, double cpuCores, double memoryGb) {
        return new ContainerResources(
                cpus,
                (int) Math.round(10 * cpuCores),
                (long) ((1L << 30) * memoryGb));
    }

    public double cpus() {
        return cpus;
    }

    // Although docker allows to update cpu quota to 0, this is not a legal value, must be set -1 for unlimited
    // See: https://github.com/docker/for-linux/issues/558
    public int cpuQuota() {
        return cpus > 0 ? (int) (cpus * CPU_PERIOD) : -1;
    }

    public int cpuPeriod() {
        return CPU_PERIOD;
    }

    public int cpuShares() {
        return cpuShares;
    }

    public long memoryBytes() {
        return memoryBytes;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ContainerResources that = (ContainerResources) o;
        return Math.abs(that.cpus - cpus) < 0.0001 &&
                cpuShares == that.cpuShares &&
                memoryBytes == that.memoryBytes;
    }

    @Override
    public int hashCode() {
        return Objects.hash(cpus, cpuShares, memoryBytes);
    }

    @Override
    public String toString() {
        return (cpus > 0 ? cpus : "unlimited") +" CPUs, " +
                (cpuShares > 0 ? cpuShares : "unlimited") + " CPU Shares, " +
                (memoryBytes > 0 ? memoryBytes + "B" : "unlimited") + " memory";
    }
}