123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- <?php
- /**
- * Performs chunks cleanup to prevent storages exhaust
- */
- class Rotator {
- /**
- * Contains alter config as key=>value
- *
- * @var array
- */
- protected $altCfg = array();
- /**
- * Contains binpaths config as key=>value
- *
- * @var array
- */
- protected $binPaths = array();
- /**
- * Contains percent of reserved space for each storage
- *
- * @var int
- */
- protected $reservedSpacePercent = 10;
- /**
- * Storages instance placeholder.
- *
- * @var object
- */
- protected $storages = '';
- /**
- * Cameras instance placeholder
- *
- * @var object
- */
- protected $cameras = '';
- /**
- * Contains full cameras data as cameraId=>fullCameraData
- *
- * @var array
- */
- protected $allCamerasData = array();
- /**
- * Contains all storages data as storageId=>storageData
- *
- * @var array
- */
- protected $allStoragesData = array();
- /**
- * Debugging mode flag
- *
- * @var bool
- */
- protected $debugFlag = false;
- /**
- * Use fast rotator for channels cleanup?
- *
- * @var bool
- */
- protected $fastFlag = false;
- /**
- * Other predefined stuff
- */
- const ROTATOR_PID = 'ROTATOR';
- const DEBUG_LOG = 'exports/rotator_debug.log';
- public function __construct() {
- $this->loadConfigs();
- $this->initStorages();
- $this->initCameras();
- }
- /**
- * Loads all required configs
- *
- * @global object $ubillingConfig
- *
- * @return void
- */
- protected function loadConfigs() {
- global $ubillingConfig;
- $this->altCfg = $ubillingConfig->getAlter();
- $this->binPaths = $ubillingConfig->getBinpaths();
- $this->reservedSpacePercent = $this->altCfg['STORAGE_RESERVED_SPACE'];
- if (isset($this->altCfg['ROTATOR_DEBUG'])) {
- if ($this->altCfg['ROTATOR_DEBUG']) {
- $this->debugFlag = true;
- }
- }
- if (isset($this->altCfg['ROTATOR_FAST'])) {
- if ($this->altCfg['ROTATOR_FAST']) {
- $this->fastFlag = true;
- }
- }
- }
- /**
- * Inits storages into protected prop for further usage
- *
- * @return void
- */
- protected function initStorages() {
- $this->storages = new Storages();
- $this->allStoragesData = $this->storages->getAllStoragesData();
- }
- /**
- * Inits cameras into protected prop and loads its full data
- *
- * @return void
- */
- protected function initCameras() {
- $this->cameras = new Cameras();
- $this->allCamerasData = $this->cameras->getAllCamerasFullData();
- }
- /**
- * Returns existing channels list available in storage depends of camera setup
- *
- * @return array
- */
- protected function getStorageChannels($storageId) {
- $result = array();
- if (isset($this->allStoragesData[$storageId])) {
- if (!empty($this->allCamerasData)) {
- foreach ($this->allCamerasData as $io => $eachCameraData) {
- if ($eachCameraData['CAMERA']['storageid'] == $storageId) {
- $storagePath = $eachCameraData['STORAGE']['path'];
- $storagePathLastChar = substr($storagePath, 0, -1);
- if ($storagePathLastChar != '/') {
- $storagePath = $storagePath . '/';
- }
- $channelName = $eachCameraData['CAMERA']['channel'];
- $channelFullPath = $storagePath . $channelName;
- if (file_exists($channelFullPath)) {
- if (is_writable($channelFullPath)) {
- $result[$channelName] = $channelFullPath;
- }
- }
- }
- }
- }
- }
- return($result);
- }
- /**
- * Just deletes oldest chunk from some channel
- *
- * @param int $storageId
- * @param string $channel
- *
- * @return string/void
- */
- protected function flushChannelOldestChunk($storageId, $channel) {
- $result = '';
- $chunksList = $this->storages->getChannelChunks($storageId, $channel);
- if (!empty($chunksList)) {
- $chunksCount = sizeof($chunksList);
- //does not kill a single chunk. It may be the current recording.
- if ($chunksCount > 1) {
- $oldestChunk = reset($chunksList);
- unlink($oldestChunk);
- $result = $oldestChunk;
- }
- }
- return($result);
- }
- /**
- * Checks, is channel locked by another background process or not?
- *
- * @param string $channelId
- *
- * @return bool
- */
- protected function channelNotLocked($channelId) {
- $result = true;
- $channelPid = new StarDust(Export::PID_EXPORT . $channelId);
- if ($channelPid->isRunning()) {
- $result = false;
- }
- return($result);
- }
- /**
- * Cleanups channel chunks until total size will be less than specified
- *
- * @param array $chunksList
- * @param int $expectedSize
- *
- * @return array
- */
- protected function flushChunksBySize($chunksList, $expectedSize) {
- $bytesFree = 0;
- $chunksDeleted = 0;
- if ((!empty($chunksList)) AND ( $expectedSize > 0)) {
- foreach ($chunksList as $eachTimeStamp => $chunksData) {
- if ($bytesFree < $expectedSize) {
- unlink($chunksData['path']);
- $chunksDeleted++;
- $bytesFree += $chunksData['size'];
- }
- }
- }
- $result = array('count' => $chunksDeleted, 'free' => $bytesFree);
- return($result);
- }
- /**
- * Performs old chunks rotation for all cameras
- *
- * @return void
- */
- public function run() {
- $rotatorProcess = new StarDust(self::ROTATOR_PID);
- if ($rotatorProcess->notRunning()) {
- $rotatorProcess->start();
- if (!empty($this->allStoragesData)) {
- foreach ($this->allStoragesData as $io => $eachStorage) {
- $storageTotalSpace = disk_total_space($eachStorage['path']);
- $storageFreeSpace = disk_free_space($eachStorage['path']);
- $usedStorageSpace = $storageTotalSpace - $storageFreeSpace;
- $maxUsagePercent = 100 - ($this->reservedSpacePercent);
- $maxUsageSpace = zb_Percent($storageTotalSpace, $maxUsagePercent);
- $mustBeFree = $storageTotalSpace - $maxUsageSpace;
- $allChannelsSpace = 0;
- //storage cleanup required?
- if ($storageFreeSpace < $mustBeFree) {
- $eachStorageChannels = $this->getStorageChannels($eachStorage['id']);
- //this storage must be cleaned
- if (!empty($eachStorageChannels)) {
- //count of channels
- $storageChannelsCount = sizeof($eachStorageChannels);
- foreach ($eachStorageChannels as $eachChannel => $chanPath) {
- $allChannelsSpace += $this->storages->getChannelSize($eachStorage['id'], $eachChannel);
- }
- $avgChanSize = $allChannelsSpace / $storageChannelsCount;
- $usedBySystem = $usedStorageSpace - $allChannelsSpace;
- //fair?
- $maxChannelAllocSize = round((($storageFreeSpace - $mustBeFree) + $allChannelsSpace) / $storageChannelsCount);
- foreach ($eachStorageChannels as $eachChannel => $chanPath) {
- if ($this->channelNotLocked($eachChannel)) {
- //
- // Fast rotator here
- //
- if ($this->fastFlag) {
- $eachChannelChunksAlloc = $this->storages->getChunksAllocSpaces($eachStorage['id'], $eachChannel);
- $eachChannelSize = $this->storages->calcChunksListSize($eachChannelChunksAlloc);
- //this channel is exhausted his reserved size?
- if ($eachChannelSize > $maxChannelAllocSize) {
- file_put_contents(self::DEBUG_LOG, curdatetime() . ' ' . wr_convertSize($eachChannelSize) . ' > OF ' . wr_convertSize($maxChannelAllocSize) . ' ' . $eachChannel . PHP_EOL, FILE_APPEND);
- $requiredToFree = $eachChannelSize - $maxChannelAllocSize;
- $cleanResult = $this->flushChunksBySize($eachChannelChunksAlloc, $requiredToFree);
- if ($this->debugFlag) {
- file_put_contents(self::DEBUG_LOG, curdatetime() . ' ' . wr_convertSize($cleanResult['free']) . ' CLEANED IN ' . $eachChannel . ' DELETED ' . $cleanResult['count'] . ' CHUNKS' . PHP_EOL, FILE_APPEND);
- }
- } else {
- //and there some rotation skips logging
- if ($this->debugFlag) {
- file_put_contents(self::DEBUG_LOG, curdatetime() . ' ' . wr_convertSize($eachChannelSize) . ' < OF ' . wr_convertSize($maxChannelAllocSize) . ' ' . $eachChannel . PHP_EOL, FILE_APPEND);
- }
- }
- } else {
- //
- // Stable rotator model
- //
- $eachChannelSize = $this->storages->getChannelSize($eachStorage['id'], $eachChannel);
- //this channel is exhausted his reserved size?
- if ($eachChannelSize > $maxChannelAllocSize) {
- while ($eachChannelSize > $maxChannelAllocSize) {
- $this->flushChannelOldestChunk($eachStorage['id'], $eachChannel);
- $eachChannelSize = $this->storages->getChannelSize($eachStorage['id'], $eachChannel);
- //some debug logging here
- if ($this->debugFlag) {
- file_put_contents(self::DEBUG_LOG, curdatetime() . ' ' . wr_convertSize($eachChannelSize) . ' > OF ' . wr_convertSize($maxChannelAllocSize) . ' ' . $eachChannel . PHP_EOL, FILE_APPEND);
- }
- }
- } else {
- //and there some rotation skips logging
- if ($this->debugFlag) {
- file_put_contents(self::DEBUG_LOG, curdatetime() . ' ' . wr_convertSize($eachChannelSize) . ' < OF ' . wr_convertSize($maxChannelAllocSize) . ' ' . $eachChannel . PHP_EOL, FILE_APPEND);
- }
- }
- }
- } else {
- //and there some rotation skips on export logging
- if ($this->debugFlag) {
- file_put_contents(self::DEBUG_LOG, curdatetime() . ' SKIPPED LOCKED BY EXPORT ' . $eachChannel . PHP_EOL, FILE_APPEND);
- }
- }
- }
- }
- }
- }
- }
- $rotatorProcess->stop();
- }
- }
- }
|