diff --git a/server/environment_docker.go b/server/environment_docker.go index 3fc30fa..6b9246b 100644 --- a/server/environment_docker.go +++ b/server/environment_docker.go @@ -21,6 +21,7 @@ import ( "path/filepath" "strconv" "strings" + "sync/atomic" "time" ) @@ -510,17 +511,19 @@ func (d *DockerEnvironment) EnableResourcePolling() error { return } + s.Resources.Lock() s.Resources.CpuAbsolute = s.Resources.CalculateAbsoluteCpu(&v.PreCPUStats, &v.CPUStats) s.Resources.Memory = s.Resources.CalculateDockerMemory(v.MemoryStats) s.Resources.MemoryLimit = v.MemoryStats.Limit + s.Resources.Unlock() // Why you ask? This already has the logic for caching disk space in use and then // also handles pushing that value to the resources object automatically. s.Filesystem.HasSpaceAvailable() for _, nw := range v.Networks { - s.Resources.Network.RxBytes += nw.RxBytes - s.Resources.Network.TxBytes += nw.TxBytes + atomic.AddUint64(&s.Resources.Network.RxBytes, nw.RxBytes) + atomic.AddUint64(&s.Resources.Network.TxBytes, nw.TxBytes) } b, _ := json.Marshal(s.Resources) @@ -539,10 +542,7 @@ func (d *DockerEnvironment) DisableResourcePolling() error { err := d.stats.Close() - d.Server.Resources.CpuAbsolute = 0 - d.Server.Resources.Memory = 0 - d.Server.Resources.Network.TxBytes = 0 - d.Server.Resources.Network.RxBytes = 0 + d.Server.Resources.Empty() return errors.WithStack(err) } diff --git a/server/filesystem.go b/server/filesystem.go index 5ea2f16..63d7986 100644 --- a/server/filesystem.go +++ b/server/filesystem.go @@ -29,10 +29,9 @@ import ( var InvalidPathResolution = errors.New("invalid path resolution") type Filesystem struct { - // The server object associated with this Filesystem. Server *Server - Configuration *config.SystemConfiguration + cacheDiskMu sync.Mutex } // Returns the root path that contains all of a server's data. @@ -171,7 +170,9 @@ func (fs *Filesystem) HasSpaceAvailable() bool { // Determine if their folder size, in bytes, is smaller than the amount of space they've // been allocated. + fs.Server.Resources.Lock() fs.Server.Resources.Disk = size + fs.Server.Resources.Unlock() // If space is -1 or 0 just return true, means they're allowed unlimited. // @@ -190,6 +191,15 @@ func (fs *Filesystem) HasSpaceAvailable() bool { // excessive IO usage. We will only walk the filesystem and determine the size of the directory if there // is no longer a cached value. func (fs *Filesystem) getCachedDiskUsage() (int64, error) { + // Obtain an exclusive lock on this process so that we don't unintentionally run it at the same + // time as another running process. Once the lock is available it'll read from the cache for the + // second call rather than hitting the disk in parallel. + // + // This effectively the same speed as running this call in parallel since this cache will return + // instantly on the second call. + fs.cacheDiskMu.Lock() + defer fs.cacheDiskMu.Unlock() + if x, exists := fs.Server.Cache.Get("disk_used"); exists { return x.(int64), nil } diff --git a/server/resources.go b/server/resources.go index 55f5cd1..b42258f 100644 --- a/server/resources.go +++ b/server/resources.go @@ -3,12 +3,15 @@ package server import ( "github.com/docker/docker/api/types" "math" + "sync" ) // Defines the current resource usage for a given server instance. If a server is offline you // should obviously expect memory and CPU usage to be 0. However, disk will always be returned // since that is not dependent on the server being running to collect that data. type ResourceUsage struct { + sync.RWMutex + // The total amount of memory, in bytes, that this server instance is consuming. This is // calculated slightly differently than just using the raw Memory field that the stats // return from the container, so please check the code setting this value for how that @@ -31,6 +34,18 @@ type ResourceUsage struct { } `json:"network"` } +// Resets the usages values to zero, used when a server is stopped to ensure we don't hold +// onto any values incorrectly. +func (ru *ResourceUsage) Empty() { + ru.Lock() + defer ru.Unlock() + + ru.Memory = 0 + ru.CpuAbsolute = 0 + ru.Network.TxBytes = 0 + ru.Network.RxBytes = 0 +} + // The "docker stats" CLI call does not return the same value as the types.MemoryStats.Usage // value which can be rather confusing to people trying to compare panel usage to // their stats output.