mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 12:59:02 +02:00

This adds the SPDX-License-Identifier license headers to the majority of our source files to make it clearer exactly which license files are under. The bulk of the files are under GPL v2.0 with one found to be under V2.0 or later, some under MIT and some have dual license. There are some files which are potentially harder to classify where we've imported upstream code and those can be handled specifically in later commits. The COPYING file is replaced with LICENSE.X files which contain the full license texts. (Bitbake rev: ff237c33337f4da2ca06c3a2c49699bc26608a6b) Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
271 lines
11 KiB
Python
271 lines
11 KiB
Python
#!/usr/bin/env python
|
|
# ex:ts=4:sw=4:sts=4:et
|
|
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
|
|
#
|
|
# Copyright (C) 2012 Robert Yang
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License version 2 as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
import os, logging, re, sys
|
|
import bb
|
|
logger = logging.getLogger("BitBake.Monitor")
|
|
|
|
def printErr(info):
|
|
logger.error("%s\n Disk space monitor will NOT be enabled" % info)
|
|
|
|
def convertGMK(unit):
|
|
|
|
""" Convert the space unit G, M, K, the unit is case-insensitive """
|
|
|
|
unitG = re.match(r'([1-9][0-9]*)[gG]\s?$', unit)
|
|
if unitG:
|
|
return int(unitG.group(1)) * (1024 ** 3)
|
|
unitM = re.match(r'([1-9][0-9]*)[mM]\s?$', unit)
|
|
if unitM:
|
|
return int(unitM.group(1)) * (1024 ** 2)
|
|
unitK = re.match(r'([1-9][0-9]*)[kK]\s?$', unit)
|
|
if unitK:
|
|
return int(unitK.group(1)) * 1024
|
|
unitN = re.match(r'([1-9][0-9]*)\s?$', unit)
|
|
if unitN:
|
|
return int(unitN.group(1))
|
|
else:
|
|
return None
|
|
|
|
def getMountedDev(path):
|
|
|
|
""" Get the device mounted at the path, uses /proc/mounts """
|
|
|
|
# Get the mount point of the filesystem containing path
|
|
# st_dev is the ID of device containing file
|
|
parentDev = os.stat(path).st_dev
|
|
currentDev = parentDev
|
|
# When the current directory's device is different from the
|
|
# parent's, then the current directory is a mount point
|
|
while parentDev == currentDev:
|
|
mountPoint = path
|
|
# Use dirname to get the parent's directory
|
|
path = os.path.dirname(path)
|
|
# Reach the "/"
|
|
if path == mountPoint:
|
|
break
|
|
parentDev= os.stat(path).st_dev
|
|
|
|
try:
|
|
with open("/proc/mounts", "r") as ifp:
|
|
for line in ifp:
|
|
procLines = line.rstrip('\n').split()
|
|
if procLines[1] == mountPoint:
|
|
return procLines[0]
|
|
except EnvironmentError:
|
|
pass
|
|
return None
|
|
|
|
def getDiskData(BBDirs, configuration):
|
|
|
|
"""Prepare disk data for disk space monitor"""
|
|
|
|
# Save the device IDs, need the ID to be unique (the dictionary's key is
|
|
# unique), so that when more than one directory is located on the same
|
|
# device, we just monitor it once
|
|
devDict = {}
|
|
for pathSpaceInode in BBDirs.split():
|
|
# The input format is: "dir,space,inode", dir is a must, space
|
|
# and inode are optional
|
|
pathSpaceInodeRe = re.match(r'([^,]*),([^,]*),([^,]*),?(.*)', pathSpaceInode)
|
|
if not pathSpaceInodeRe:
|
|
printErr("Invalid value in BB_DISKMON_DIRS: %s" % pathSpaceInode)
|
|
return None
|
|
|
|
action = pathSpaceInodeRe.group(1)
|
|
if action not in ("ABORT", "STOPTASKS", "WARN"):
|
|
printErr("Unknown disk space monitor action: %s" % action)
|
|
return None
|
|
|
|
path = os.path.realpath(pathSpaceInodeRe.group(2))
|
|
if not path:
|
|
printErr("Invalid path value in BB_DISKMON_DIRS: %s" % pathSpaceInode)
|
|
return None
|
|
|
|
# The disk space or inode is optional, but it should have a correct
|
|
# value once it is specified
|
|
minSpace = pathSpaceInodeRe.group(3)
|
|
if minSpace:
|
|
minSpace = convertGMK(minSpace)
|
|
if not minSpace:
|
|
printErr("Invalid disk space value in BB_DISKMON_DIRS: %s" % pathSpaceInodeRe.group(3))
|
|
return None
|
|
else:
|
|
# None means that it is not specified
|
|
minSpace = None
|
|
|
|
minInode = pathSpaceInodeRe.group(4)
|
|
if minInode:
|
|
minInode = convertGMK(minInode)
|
|
if not minInode:
|
|
printErr("Invalid inode value in BB_DISKMON_DIRS: %s" % pathSpaceInodeRe.group(4))
|
|
return None
|
|
else:
|
|
# None means that it is not specified
|
|
minInode = None
|
|
|
|
if minSpace is None and minInode is None:
|
|
printErr("No disk space or inode value in found BB_DISKMON_DIRS: %s" % pathSpaceInode)
|
|
return None
|
|
# mkdir for the directory since it may not exist, for example the
|
|
# DL_DIR may not exist at the very beginning
|
|
if not os.path.exists(path):
|
|
bb.utils.mkdirhier(path)
|
|
dev = getMountedDev(path)
|
|
# Use path/action as the key
|
|
devDict[(path, action)] = [dev, minSpace, minInode]
|
|
|
|
return devDict
|
|
|
|
def getInterval(configuration):
|
|
|
|
""" Get the disk space interval """
|
|
|
|
# The default value is 50M and 5K.
|
|
spaceDefault = 50 * 1024 * 1024
|
|
inodeDefault = 5 * 1024
|
|
|
|
interval = configuration.getVar("BB_DISKMON_WARNINTERVAL")
|
|
if not interval:
|
|
return spaceDefault, inodeDefault
|
|
else:
|
|
# The disk space or inode interval is optional, but it should
|
|
# have a correct value once it is specified
|
|
intervalRe = re.match(r'([^,]*),?\s*(.*)', interval)
|
|
if intervalRe:
|
|
intervalSpace = intervalRe.group(1)
|
|
if intervalSpace:
|
|
intervalSpace = convertGMK(intervalSpace)
|
|
if not intervalSpace:
|
|
printErr("Invalid disk space interval value in BB_DISKMON_WARNINTERVAL: %s" % intervalRe.group(1))
|
|
return None, None
|
|
else:
|
|
intervalSpace = spaceDefault
|
|
intervalInode = intervalRe.group(2)
|
|
if intervalInode:
|
|
intervalInode = convertGMK(intervalInode)
|
|
if not intervalInode:
|
|
printErr("Invalid disk inode interval value in BB_DISKMON_WARNINTERVAL: %s" % intervalRe.group(2))
|
|
return None, None
|
|
else:
|
|
intervalInode = inodeDefault
|
|
return intervalSpace, intervalInode
|
|
else:
|
|
printErr("Invalid interval value in BB_DISKMON_WARNINTERVAL: %s" % interval)
|
|
return None, None
|
|
|
|
class diskMonitor:
|
|
|
|
"""Prepare the disk space monitor data"""
|
|
|
|
def __init__(self, configuration):
|
|
|
|
self.enableMonitor = False
|
|
self.configuration = configuration
|
|
|
|
BBDirs = configuration.getVar("BB_DISKMON_DIRS") or None
|
|
if BBDirs:
|
|
self.devDict = getDiskData(BBDirs, configuration)
|
|
if self.devDict:
|
|
self.spaceInterval, self.inodeInterval = getInterval(configuration)
|
|
if self.spaceInterval and self.inodeInterval:
|
|
self.enableMonitor = True
|
|
# These are for saving the previous disk free space and inode, we
|
|
# use them to avoid printing too many warning messages
|
|
self.preFreeS = {}
|
|
self.preFreeI = {}
|
|
# This is for STOPTASKS and ABORT, to avoid printing the message
|
|
# repeatedly while waiting for the tasks to finish
|
|
self.checked = {}
|
|
for k in self.devDict:
|
|
self.preFreeS[k] = 0
|
|
self.preFreeI[k] = 0
|
|
self.checked[k] = False
|
|
if self.spaceInterval is None and self.inodeInterval is None:
|
|
self.enableMonitor = False
|
|
|
|
def check(self, rq):
|
|
|
|
""" Take action for the monitor """
|
|
|
|
if self.enableMonitor:
|
|
diskUsage = {}
|
|
for k, attributes in self.devDict.items():
|
|
path, action = k
|
|
dev, minSpace, minInode = attributes
|
|
|
|
st = os.statvfs(path)
|
|
|
|
# The available free space, integer number
|
|
freeSpace = st.f_bavail * st.f_frsize
|
|
|
|
# Send all relevant information in the event.
|
|
freeSpaceRoot = st.f_bfree * st.f_frsize
|
|
totalSpace = st.f_blocks * st.f_frsize
|
|
diskUsage[dev] = bb.event.DiskUsageSample(freeSpace, freeSpaceRoot, totalSpace)
|
|
|
|
if minSpace and freeSpace < minSpace:
|
|
# Always show warning, the self.checked would always be False if the action is WARN
|
|
if self.preFreeS[k] == 0 or self.preFreeS[k] - freeSpace > self.spaceInterval and not self.checked[k]:
|
|
logger.warning("The free space of %s (%s) is running low (%.3fGB left)" % \
|
|
(path, dev, freeSpace / 1024 / 1024 / 1024.0))
|
|
self.preFreeS[k] = freeSpace
|
|
|
|
if action == "STOPTASKS" and not self.checked[k]:
|
|
logger.error("No new tasks can be executed since the disk space monitor action is \"STOPTASKS\"!")
|
|
self.checked[k] = True
|
|
rq.finish_runqueue(False)
|
|
bb.event.fire(bb.event.DiskFull(dev, 'disk', freeSpace, path), self.configuration)
|
|
elif action == "ABORT" and not self.checked[k]:
|
|
logger.error("Immediately abort since the disk space monitor action is \"ABORT\"!")
|
|
self.checked[k] = True
|
|
rq.finish_runqueue(True)
|
|
bb.event.fire(bb.event.DiskFull(dev, 'disk', freeSpace, path), self.configuration)
|
|
|
|
# The free inodes, integer number
|
|
freeInode = st.f_favail
|
|
|
|
if minInode and freeInode < minInode:
|
|
# Some filesystems use dynamic inodes so can't run out
|
|
# (e.g. btrfs). This is reported by the inode count being 0.
|
|
if st.f_files == 0:
|
|
self.devDict[k][2] = None
|
|
continue
|
|
# Always show warning, the self.checked would always be False if the action is WARN
|
|
if self.preFreeI[k] == 0 or self.preFreeI[k] - freeInode > self.inodeInterval and not self.checked[k]:
|
|
logger.warning("The free inode of %s (%s) is running low (%.3fK left)" % \
|
|
(path, dev, freeInode / 1024.0))
|
|
self.preFreeI[k] = freeInode
|
|
|
|
if action == "STOPTASKS" and not self.checked[k]:
|
|
logger.error("No new tasks can be executed since the disk space monitor action is \"STOPTASKS\"!")
|
|
self.checked[k] = True
|
|
rq.finish_runqueue(False)
|
|
bb.event.fire(bb.event.DiskFull(dev, 'inode', freeInode, path), self.configuration)
|
|
elif action == "ABORT" and not self.checked[k]:
|
|
logger.error("Immediately abort since the disk space monitor action is \"ABORT\"!")
|
|
self.checked[k] = True
|
|
rq.finish_runqueue(True)
|
|
bb.event.fire(bb.event.DiskFull(dev, 'inode', freeInode, path), self.configuration)
|
|
|
|
bb.event.fire(bb.event.MonitorDiskEvent(diskUsage), self.configuration)
|
|
return
|