""" This is used to cache already generated plots in order to improve the response time and increase reusability. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function __RCSID__ = "c59810311 (2021-10-01 10:44:25 +0200) Chris Burr " import os import hashlib import time import threading from DIRAC import S_OK, gLogger from DIRAC.Core.Utilities.ThreadSafe import Synchronizer from DIRAC.FrameworkSystem.private.monitoring.RRDManager import RRDManager from DIRAC.FrameworkSystem.Client.MonitoringClient import gMonitor gSynchro = Synchronizer() class PlotCache(object): """ This is generally used for caching the plots. """ # This class is basically used to cache the graphs that are generated once so if the user tries to generate them again # the entire process of graph generation using RRDManager is not performed instead a cached version of the graph is # returned to reduce the response time. def __init__(self, rrdManager=None): if rrdManager is None: self.rrdManager = RRDManager else: self.rrdManager = rrdManager self.plotsLocation = self.rrdManager.getGraphLocation() for plot in os.listdir(self.plotsLocation): if plot.find(".png") > 0: os.unlink("%s/%s" % (self.plotsLocation, plot)) self.cachedPlots = {} self.alive = True self.graceTime = 60 self.purgeThread = threading.Thread(target=self.purgeCached) self.purgeThread.start() def __generateName(self, *args, **kwargs): m = hashlib.md5() m.update(repr(args).encode()) m.update(repr(kwargs).encode()) return m.hexdigest() def __isCurrentTime(self, toSecs): currentBucket = self.rrdManager.getCurrentBucketTime(self.graceTime) return toSecs + self.graceTime > currentBucket def purgeCached(self): while self.alive: time.sleep(self.graceTime * 2) self.__purgeExpiredGraphs() @gSynchro def __refreshGraph(self, graphFile): self.cachedPlots[graphFile][1] = time.time() @gSynchro def __registerGraph(self, graphFile, fromSecs, toSecs): if self.__isCurrentTime(toSecs): cacheTime = self.graceTime * 2 else: cacheTime = 3600 self.cachedPlots[graphFile] = [cacheTime, time.time()] @gSynchro def __isCacheGraph(self, graphFile): return graphFile in self.cachedPlots @gSynchro def __purgeExpiredGraphs(self): now = time.time() graphsToDelete = [] for cachedFile in self.cachedPlots: fileData = self.cachedPlots[cachedFile] if fileData[0] + fileData[1] < now: graphsToDelete.append(cachedFile) for cachedFile in graphsToDelete: try: filePath = "%s/%s" % (self.plotsLocation, cachedFile) os.unlink(filePath) except Exception as e: gLogger.error("Can't delete plot file", "%s: %s" % (filePath, str(e))) del self.cachedPlots[cachedFile] def groupPlot(self, *args): """ This method is called by the __generateGroupPlots method of the ServiceInterface in order to cache the generated group plots. """ return self.__getGraph(getattr(self.rrdManager, "groupPlot"), args) def plot(self, *args): """ This method is called by the __generatePlots method of the ServiceInterface in order to cache the generated plot. """ return self.__getGraph(getattr(self.rrdManager, "plot"), args) def __getGraph(self, plotFunc, args): fromSecs = args[0] toSecs = args[1] graphFile = "%s-%s-%s.png" % ( self.__generateName(*args[2:]), self.rrdManager.bucketize(fromSecs, self.graceTime), self.rrdManager.bucketize(toSecs, self.graceTime), ) if self.__isCacheGraph(graphFile): self.__refreshGraph(graphFile) gLogger.info("Cached graph file %s" % graphFile) gMonitor.addMark("cachedplots") return S_OK(graphFile) else: gMonitor.addMark("drawnplots") self.__registerGraph(graphFile, fromSecs, toSecs) return plotFunc(graphFilename=graphFile, *args)