Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
pl.touk.nussknacker.ui.statistics.UsageStatisticsReportsSettingsService.scala Maven / Gradle / Ivy
package pl.touk.nussknacker.ui.statistics
import cats.data.EitherT
import com.typesafe.scalalogging.LazyLogging
import pl.touk.nussknacker.engine.api.component.{DesignerWideComponentId, ProcessingMode}
import pl.touk.nussknacker.engine.api.deployment.{ProcessAction, StateStatus}
import pl.touk.nussknacker.engine.api.graph.ScenarioGraph
import pl.touk.nussknacker.engine.api.process.{ProcessId, VersionId}
import pl.touk.nussknacker.engine.definition.component.ComponentDefinitionWithImplementation
import pl.touk.nussknacker.engine.graph.node.FragmentInput
import pl.touk.nussknacker.engine.version.BuildInfo
import pl.touk.nussknacker.ui.config.UsageStatisticsReportsConfig
import pl.touk.nussknacker.ui.db.timeseries.{FEStatisticsRepository, ReadFEStatisticsRepository}
import pl.touk.nussknacker.ui.definition.component.ComponentService
import pl.touk.nussknacker.ui.process.ProcessService.GetScenarioWithDetailsOptions
import pl.touk.nussknacker.ui.process.processingtype.{DeploymentManagerType, ProcessingTypeDataProvider}
import pl.touk.nussknacker.ui.process.repository.ProcessActivityRepository
import pl.touk.nussknacker.ui.process.{ProcessService, ScenarioQuery}
import pl.touk.nussknacker.ui.security.api.{LoggedUser, NussknackerInternalUser}
import pl.touk.nussknacker.ui.statistics.UsageStatisticsReportsSettingsService.nuFingerprintFileName
import java.time.Clock
import scala.concurrent.{ExecutionContext, Future}
object UsageStatisticsReportsSettingsService extends LazyLogging {
private val nuFingerprintFileName = new FileName("nussknacker.fingerprint")
def apply(
config: UsageStatisticsReportsConfig,
processService: ProcessService,
// TODO: Instead of passing deploymentManagerTypes next to processService, we should split domain ScenarioWithDetails from DTOs - see comment in ScenarioWithDetails
deploymentManagerTypes: ProcessingTypeDataProvider[DeploymentManagerType, _],
fingerprintService: FingerprintService,
scenarioActivityRepository: ProcessActivityRepository,
componentService: ComponentService,
statisticsRepository: FEStatisticsRepository[Future],
componentList: List[ComponentDefinitionWithImplementation],
designerClock: Clock
)(implicit ec: ExecutionContext): UsageStatisticsReportsSettingsService = {
val ignoringErrorsFEStatisticsRepository = new IgnoringErrorsFEStatisticsRepository(statisticsRepository)
implicit val user: LoggedUser = NussknackerInternalUser.instance
def fetchNonArchivedScenarioParameters(): Future[Either[StatisticError, List[ScenarioStatisticsInputData]]] = {
// TODO: Warning, here is a security leak. We report statistics in the scope of processing types to which
// given user has no access rights.
val deploymentManagerTypeByProcessingType = deploymentManagerTypes.all(user)
processService
.getLatestProcessesWithDetails(
ScenarioQuery.unarchived,
GetScenarioWithDetailsOptions.withsScenarioGraph.withFetchState
)(user)
.map { scenariosDetails =>
Right(
scenariosDetails.map(scenario =>
ScenarioStatisticsInputData(
isFragment = scenario.isFragment,
processingMode = scenario.processingMode,
deploymentManagerType = deploymentManagerTypeByProcessingType(scenario.processingType),
status = scenario.state.map(_.status),
nodesCount = scenario.scenarioGraph.map(_.nodes.length).getOrElse(0),
scenarioCategory = scenario.processCategory,
scenarioVersion = scenario.processVersionId,
createdBy = scenario.createdBy,
fragmentsUsedCount = getFragmentsUsedInScenario(scenario.scenarioGraph),
lastDeployedAction = scenario.lastDeployedAction,
scenarioId = scenario.processId
)
)
)
}
}
def fetchActivity(): Future[Map[String, Int]] = {
scenarioActivityRepository.getActivityStats
}
def fetchComponentUsage(): Future[Map[DesignerWideComponentId, Long]] = {
componentService.getUsagesPerDesignerWideComponentId
}
new UsageStatisticsReportsSettingsService(
config,
fingerprintService,
fetchNonArchivedScenarioParameters,
fetchActivity,
() => ignoringErrorsFEStatisticsRepository.read(),
componentList,
fetchComponentUsage,
designerClock
)
}
private def getFragmentsUsedInScenario(scenarioGraph: Option[ScenarioGraph]): Int = {
scenarioGraph match {
case Some(graph) =>
graph.nodes.map {
case _: FragmentInput => 1
case _ => 0
}.sum
case None => 0
}
}
}
class UsageStatisticsReportsSettingsService(
config: UsageStatisticsReportsConfig,
fingerprintService: FingerprintService,
fetchNonArchivedScenariosInputData: () => Future[Either[StatisticError, List[ScenarioStatisticsInputData]]],
fetchActivity: () => Future[Map[String, Int]],
fetchFeStatistics: () => Future[RawFEStatistics],
components: List[ComponentDefinitionWithImplementation],
componentUsage: () => Future[Map[DesignerWideComponentId, Long]],
designerClock: Clock
)(implicit ec: ExecutionContext) {
private val designerStartTime = designerClock.instant()
def prepareStatisticsUrl(): Future[Either[StatisticError, Statistics]] = {
if (config.enabled) {
determineStatistics().value
} else {
Future.successful(Right(Statistics.Empty))
}
}
private def determineStatistics(): EitherT[Future, StatisticError, Statistics] = {
for {
scenariosInputData <- new EitherT(fetchNonArchivedScenariosInputData())
scenariosStatistics = ScenarioStatistics.getScenarioStatistics(scenariosInputData)
basicStatistics = determineBasicStatistics(config)
generalStatistics = ScenarioStatistics.getGeneralStatistics(scenariosInputData)
attachmentsAndCommentsTotal <- EitherT.liftF(fetchActivity())
activityStatistics = ScenarioStatistics.getActivityStatistics(
attachmentsAndCommentsTotal,
scenariosInputData.length
)
componentDesignerWideUsage <- EitherT.liftF(componentUsage())
componentStatistics = ScenarioStatistics.getComponentStatistics(componentDesignerWideUsage, components)
feStatistics <- EitherT.liftF(fetchFeStatistics())
designerUptimeStatistics = getDesignerUptimeStatistics
fingerprint <- new EitherT(fingerprintService.fingerprint(config, nuFingerprintFileName))
requestId = RequestId()
combinedStatistics = basicStatistics ++
scenariosStatistics ++
generalStatistics ++
activityStatistics ++
componentStatistics ++
designerUptimeStatistics ++
feStatistics.raw.map { case (k, v) =>
k -> v.toString
}
} yield new Statistics.NonEmpty(fingerprint, requestId, combinedStatistics)
}
private def determineBasicStatistics(
config: UsageStatisticsReportsConfig
): Map[String, String] =
Map(
// If it is not set, we assume that it is some custom build from source code
NuSource.name -> config.source.filterNot(_.isBlank).getOrElse("sources"),
NuVersion.name -> BuildInfo.version
)
private def getDesignerUptimeStatistics: Map[String, String] = {
Map(
DesignerUptimeInSeconds.name -> (designerClock
.instant()
.getEpochSecond - designerStartTime.getEpochSecond).toString
)
}
}
private[statistics] case class ScenarioStatisticsInputData(
isFragment: Boolean,
processingMode: ProcessingMode,
deploymentManagerType: DeploymentManagerType,
// For fragments status is empty
status: Option[StateStatus],
nodesCount: Int,
scenarioCategory: String,
scenarioVersion: VersionId,
createdBy: String,
fragmentsUsedCount: Int,
lastDeployedAction: Option[ProcessAction],
scenarioId: Option[ProcessId]
)
private[statistics] class IgnoringErrorsFEStatisticsRepository(repository: FEStatisticsRepository[Future])(
implicit ec: ExecutionContext
) extends ReadFEStatisticsRepository[Future]
with LazyLogging {
override def read(): Future[RawFEStatistics] = repository
.read()
.recover { case ex: Exception =>
logger.warn("Exception occurred during statistics read", ex)
RawFEStatistics.empty
}
}