diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityRepositoryModules.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityRepositoryModules.kt index b6605f34dbb8..604bbfb1bd79 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityRepositoryModules.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityRepositoryModules.kt @@ -634,21 +634,6 @@ object CommunityRepositoryModules { } } - fun aeDatabasePlugin(mainModuleName: String, extraModules: List = emptyList()): PluginLayout { - return plugin(mainModuleName) { spec -> - spec.directoryName = "ae-database" - spec.mainJarName = "ae-database.jar" - spec.withModules(listOf( - "intellij.ae.database.core", - "intellij.ae.database.counters.community" - )) - spec.bundlingRestrictions.includeInDistribution = PluginDistribution.ALL - if (extraModules.isNotEmpty()) { - spec.withModules(extraModules) - } - } - } - fun githubPlugin(mainModuleName: String, productCode: String): PluginLayout { return plugin(mainModuleName) { spec -> spec.directoryName = "vcs-github-$productCode" diff --git a/plugins/feature-usage-database/core/BUILD.bazel b/plugins/feature-usage-database/core/BUILD.bazel deleted file mode 100644 index 0e2d9ac0b812..000000000000 --- a/plugins/feature-usage-database/core/BUILD.bazel +++ /dev/null @@ -1,53 +0,0 @@ -### auto-generated section `build intellij.ae.database.core` start -load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_resources", "jvm_test") - -jvm_resources( - name = "ae-database-core_resources", - files = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -jvm_library( - name = "ae-database-core", - module_name = "intellij.ae.database.core", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java"], allow_empty = True), - deps = [ - "@lib//:kotlin-stdlib", - "//platform/util-ex", - "//platform/projectModel-api:projectModel", - "//platform/statistics", - "//platform/core-api:core", - "//platform/sqlite", - "//platform/util", - ], - runtime_deps = [":ae-database-core_resources"] -) - -jvm_library( - name = "ae-database-core_test_lib", - visibility = ["//visibility:public"], - srcs = glob(["tests/**/*.kt", "tests/**/*.java"], allow_empty = True), - associates = [":ae-database-core"], - deps = [ - "@lib//:kotlin-stdlib", - "//platform/util-ex", - "//platform/projectModel-api:projectModel", - "//platform/statistics", - "//platform/statistics:statistics_test_lib", - "//platform/core-api:core", - "//platform/sqlite", - "//platform/sqlite:sqlite_test_lib", - "//platform/util", - "//platform/testFramework", - "//platform/testFramework:testFramework_test_lib", - "@lib//:assert_j", - ], - runtime_deps = [":ae-database-core_resources"] -) - -jvm_test( - name = "ae-database-core_test", - runtime_deps = [":ae-database-core_test_lib"] -) -### auto-generated section `build intellij.ae.database.core` end \ No newline at end of file diff --git a/plugins/feature-usage-database/core/intellij.ae.database.core.iml b/plugins/feature-usage-database/core/intellij.ae.database.core.iml deleted file mode 100644 index 5ce564ac193f..000000000000 --- a/plugins/feature-usage-database/core/intellij.ae.database.core.iml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/plugins/feature-usage-database/core/resources/intellij.ae.database.core.xml b/plugins/feature-usage-database/core/resources/intellij.ae.database.core.xml deleted file mode 100644 index bd80614ea37e..000000000000 --- a/plugins/feature-usage-database/core/resources/intellij.ae.database.core.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/AEDatabaseLifetime.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/AEDatabaseLifetime.kt deleted file mode 100644 index fe64deebdcc1..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/AEDatabaseLifetime.kt +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core - -import com.intellij.openapi.Disposable -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import kotlinx.coroutines.CoroutineScope - -@Service -internal class AEDatabaseLifetime(private val coroutineScope: CoroutineScope) : Disposable { - companion object { - fun getScope() = ApplicationManager.getApplication().service().coroutineScope - fun getDisposable(): Disposable = ApplicationManager.getApplication().service() - fun getDisposable(project: Project): Disposable = project.service() - } - - override fun dispose() {} -} - -@Service(Service.Level.PROJECT) -internal class AEDatabaseProjectLifetime : Disposable { - override fun dispose() {} -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/IdService.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/IdService.kt deleted file mode 100644 index c8c7e773d826..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/IdService.kt +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core - -import com.intellij.ae.database.core.dbs.SqliteDatabaseMetadata -import com.intellij.internal.statistic.eventLog.fus.MachineIdManager -import com.intellij.openapi.application.ApplicationInfo -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.* -import com.intellij.openapi.diagnostic.logger -import java.util.* - -private val logger = logger() - -@Service(Service.Level.APP) -@State(name = "AEIdeId", storages = [Storage("aeIdeId.xml", roamingType = RoamingType.DISABLED)], reportStatistic = false) -class IdService : PersistentStateComponentWithModificationTracker { - class State: BaseState() { - var id: String? by string("undefined") - } - - companion object { - fun getInstance() = ApplicationManager.getApplication().service() - suspend fun getInstanceAsync() = ApplicationManager.getApplication().serviceAsync() - } - - val id get() = state.id ?: run { - logger.error("id was not defined") - "undefined" - } - - val machineId by lazy { MachineIdManager.getAnonymizedMachineId("com.intellij.platform.ae.database") ?: "undefined" } - - fun getDatabaseId(metadata: SqliteDatabaseMetadata) = metadata.ideId - - val ideCode by lazy { - ApplicationInfo.getInstance().build.productCode.ifBlank { - if (ApplicationManager.getApplication().isUnitTestMode) "unittest" - else { - logger.warn("productCode is blank, will use 'unknown' value") - "unknown" - } - } - } - - private var myState = State() - - override fun getState() = myState - override fun getStateModificationCount() = state.modificationCount - - override fun loadState(state: State) { - myState = state - } - - override fun noStateLoaded() { - myState.id = UUID.randomUUID().toString() - } -} diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/IdServiceTempPreloader.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/IdServiceTempPreloader.kt deleted file mode 100644 index 43c927ca1b27..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/IdServiceTempPreloader.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.intellij.ae.database.core - -import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.ProjectActivity - -private class IdServiceTempPreloader : ProjectActivity { - override suspend fun execute(project: Project) { - IdService.getInstanceAsync().id - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/CounterUserActivity.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/CounterUserActivity.kt deleted file mode 100644 index e8f551304c6d..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/CounterUserActivity.kt +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.activities - -import com.intellij.ae.database.core.dbs.counter.CounterUserActivityDatabase -import com.intellij.ae.database.core.utils.InstantUtils -import java.time.Instant - - -interface CounterUserActivity : UserActivity - -abstract class DatabaseBackedCounterUserActivity : CounterUserActivity { - protected suspend fun getDatabase() = CounterUserActivityDatabase.getInstanceAsync() -} - -abstract class WritableDatabaseBackedCounterUserActivity : DatabaseBackedCounterUserActivity() { - protected suspend fun submit(diff: Int, eventTime: Instant = InstantUtils.Now) { - getDatabase().submit(this, diff, eventTime) - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/TimeSpanUserActivity.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/TimeSpanUserActivity.kt deleted file mode 100644 index d18db7beace7..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/TimeSpanUserActivity.kt +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.activities - -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabase -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabaseManualKind -import java.time.Instant - -interface TimeSpanUserActivity : UserActivity - -abstract class DatabaseBackedTimeSpanUserActivity : TimeSpanUserActivity { - protected val coroutineScope get() = com.intellij.ae.database.core.AEDatabaseLifetime.getScope() - protected suspend fun getDatabase() = TimeSpanUserActivityDatabase.getInstanceAsync() -} - -abstract class WritableDatabaseBackedTimeSpanUserActivity : DatabaseBackedTimeSpanUserActivity() { - abstract val canBeStale: Boolean - - protected suspend fun submitManual(id: String, kind: TimeSpanUserActivityDatabaseManualKind, extra: Map?, moment: Instant? = null) { - getDatabase().submitManual(this, id, kind, canBeStale, extra, moment) - } - - protected suspend fun submitPeriodic(id: String, extra: Map? = null) { - getDatabase().submitPeriodicEvent(this, id, extra) - } -} - -fun DatabaseBackedTimeSpanUserActivity.toKey(extraKey: String) = "${id}_${extraKey}" \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/UserActivity.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/UserActivity.kt deleted file mode 100644 index 4283fc211c68..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/UserActivity.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.intellij.ae.database.core.activities - -/** - * Base interface for user activity – nothing but an ID. - * - * Any class with [UserActivity] interface should be: - * 1. a Kotlin object - * 2. stateless - * - * You don't want to implement this interface. Instead, implement: - * * [WritableDatabaseBackedCounterUserActivity]: a counter user activity that can be stored in a database - * * [WritableDatabaseBackedTimeSpanUserActivity]: a time span user activity that can be stored in a database - * * [ReadableUserActivity]: if your activity can return a result - * * [DatabaseBackedCounterUserActivity] / [DatabaseBackedTimeSpanUserActivity]: useful for readable user activities, - * if you need to access database (it most cases you would like to do so) - * - * =-=-=-=-=-=-=-=-=-=-=-=-= - * - * Implementation example 1: - * - * For an upcoming campaign you need to implement a feature counter: 'The longest streak of IDE opening'. - * To achieve the goal you need to implement the following objects: - * 1. a [WritableDatabaseBackedTimeSpanUserActivity] that records each time IDE is started. This activity doesn't return any information, it's - * write only. Question to careful readers: what should it return? - * 2. a [ReadableUserActivity] that calculates the longest streak of IDE opening based on data from activity (1). This activity by itself - * doesn't store any data - * - * When the developer will be asked to calculate some new value and it can be calculated with data about when IDE was running, they can easily - * reuse data from user activity (1) - */ -sealed interface UserActivity { - val id: String -} - -/** - * Represents an activity that can return some sort of value or several values. - */ -interface ReadableUserActivity : UserActivity { - /** - * Returns a value calculated for this activity. You are free to implement other - * 'getter' methods in your class, but this method should return some sort of - * default value - */ - suspend fun getActivityValue(): TResult -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/package-info.java deleted file mode 100644 index 2f2eeb29a0d7..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/activities/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core.activities; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/FusBasedCounterUserActivity.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/FusBasedCounterUserActivity.kt deleted file mode 100644 index ef1292dd3ea4..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/FusBasedCounterUserActivity.kt +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.baseEvents - -import com.intellij.ae.database.core.activities.WritableDatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.baseEvents.fus.FusEventCatcher -import com.intellij.ae.database.core.baseEvents.fus.FusEventDefinitionBuilder -import java.time.Instant - -/** - * FUS-based counter user activity. You should define the event with [define] method. - * - * You need to register your activity in XML - * - * @see com.intellij.ae.database.baseEvents.fus.FusEventCatcher - */ -abstract class FusBasedCounterUserActivity : WritableDatabaseBackedCounterUserActivity() { - protected val catcher = object : FusEventCatcher() { - override fun define(): FusEventDefinitionBuilder { - return this@FusBasedCounterUserActivity.define() - } - - override suspend fun onEvent(fields: Map, eventTime: Instant) { - getDatabase().submit(this@FusBasedCounterUserActivity, getIncrementValue(fields), eventTime) - } - } - - final override val id: String by lazy { catcher.definition.id } - - /** - * Override this method if your need to do custom calculations over FUS data - * - * Example 1: - * You want to write down every debug session start. In this case '1' is good for you here, no need to override - * - * Example 2: - * You want to calculate how many characters completion saved. You need to subtract 'typing' field from 'token_length' field. - * Of course, don't forget null checks. The resulting method should look something like this: - * ``` - * val tokenLen = fields["token_length"] as? Int - * val typing = fields["typing"] as? Int - * if (tokenLen == null || typing == null) { - * thisLogger().error("One of required fields is null") - * return 1 - * } - * return tokenLen - typing - * ``` - */ - protected open fun getIncrementValue(fields: Map) = 1 - - /** - * @see com.intellij.ae.database.baseEvents.fus.FusEventCatcher.define - */ - protected abstract fun define(): FusEventDefinitionBuilder - - /** - * @see com.intellij.ae.database.baseEvents.fus.FusEventCatcher.definition - */ - protected fun definition(id: String, x: FusEventDefinitionBuilder.() -> Unit) = catcher.definition(id, x) -} - diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/fus/FusEventCatcher.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/fus/FusEventCatcher.kt deleted file mode 100644 index a3034170c76c..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/fus/FusEventCatcher.kt +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.baseEvents.fus - -import com.intellij.openapi.extensions.ExtensionPointName -import java.time.Instant - -data class FusEventDefinitionField( - val type: Class, - val comparator: (T) -> Boolean -) - -internal data class FusEventDefinition( - val id: String, - val group: String, - val event: String, - val fields: Map> -) - -class FusEventDefinitionBuilder(private val id: String) { - val fields = mutableMapOf>() - private var myGroup: String? = null - private var myEvent: String? = null - - inner class FieldsBuilder { - inline fun field(name: String, value: T) = field(name) { it == value } - - inline fun field(name: String, noinline comparator: (T) -> Boolean) { - fields[name] = FusEventDefinitionField(T::class.java, comparator) - } - } - - fun event(group: String, event: String, x: FieldsBuilder.() -> Unit) { - if (myGroup != null) error("event() can be called only once (at least for now)") - - myGroup = group - myEvent = event - - x(FieldsBuilder()) - } - - fun event(group: String, event: String) { - event(group, event) {} - } - - internal fun build(): FusEventDefinition { - val group = myGroup ?: error("Group is not defined") - val event = myEvent ?: error("Event is not defined") - - @Suppress("UNCHECKED_CAST") - return FusEventDefinition(id, group, event, fields as Map>) - } -} - -/** - * A class that allows to catch specific FUS event. You should define the event with [define] method - * - * You need to register your activity in XML: - * ``` - * - * ``` - * - * `implementation` is a path to [Factory] class. Note the dollar symbol at the end. - */ -abstract class FusEventCatcher { - companion object { - val EP_NAME = ExtensionPointName.create("com.intellij.ae.database.fusEventCatcher") - } - - interface Factory { - fun getInstance(): FusEventCatcher - } - - internal val definition: FusEventDefinition by lazy { define().build() } - - fun definition(id: String, x: FusEventDefinitionBuilder.() -> Unit): FusEventDefinitionBuilder { - return FusEventDefinitionBuilder(id).apply(x) - } - - /** - * A definition of event. - * - * Starts with [definition] function. It accepts 'id' as an argument – it will be used in [id] object field; and a lambda. - * Lambda contains definition of FUS event. You should call [FusEventDefinitionBuilder.event] method and pass event group, event id and a lambda. - * This lambda contains a list of fields that should be present in an event. - * - * Example: - * ``` - * definition("sampleEvent") { - * event("toolwindow", "activated") { - * field("id", "Project") - * field("invocation") { it > 10 } - * } - * } - * ``` - * - * It defines user activity "sampleEvent", which is FUS event "toolwindow.activated" with fields "id" = "Project" and "invocation" > 10. - * Note that logical condition between fields is 'AND' – all fields should satisfy the condition - */ - protected abstract fun define(): FusEventDefinitionBuilder - - /** - * Perform an action when FUS event from [define] occurs - */ - abstract suspend fun onEvent(fields: Map, eventTime: Instant) -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/fus/FusEventCatcherService.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/fus/FusEventCatcherService.kt deleted file mode 100644 index 1bb0b4b77a3b..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/fus/FusEventCatcherService.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.intellij.ae.database.core.baseEvents.fus - -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.internal.statistic.eventLog.EventLogListenersManager -import com.intellij.internal.statistic.eventLog.ExternalEventLogListenerProviderExtension -import com.intellij.internal.statistic.eventLog.StatisticsEventLogListener -import com.intellij.openapi.Disposable -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.components.serviceIfCreated -import com.intellij.util.asSafely -import com.jetbrains.fus.reporting.model.lion3.LogEvent -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import java.time.Instant - -/** - * Service that receives all FUS events and tries to find suitable [FusEventCatcher] - * - * TODO: lifetime of this service is longer than lifetime of [FusEventCatcher], because a catcher can be implemented inside a plugin, - * TODO: but an EP is marked is marked as non-dynamic and there is no catchers outside impl-detail plugin - */ -@Service(Service.Level.APP) -internal class FusEventCatcherService(cs: CoroutineScope) { - companion object { - internal fun getInstance() = ApplicationManager.getApplication().service() - } - - private data class EventSubmission(val group: String, val event: String, val fields: Map, val time: Instant) - - private val eventsSubmitFlow = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - - // group to event - private val catchers = mutableMapOf, MutableList>() - private val catchersLock = Mutex() - - init { - cs.launch { - eventsSubmitFlow.collect { - val catchers = find(it.group, it.event, it.fields) - for (catcher in catchers) { - catcher.onEvent(it.fields, it.time) - } - } - } - } - - fun submit(group: String, event: String, fields: Map) { - val time = fields["created"]?.asSafely()?.let { Instant.ofEpochMilli(it) } ?: InstantUtils.Now - eventsSubmitFlow.tryEmit(EventSubmission(group, event, fields, time)) - } - - private suspend fun find(group: String, event: String, fields: Map): List { - val suitableCatchers = catchersLock.withLock { - if (catchers.isEmpty()) { - initCatchers() - } - catchers[group to event] - } - - return suitableCatchers?.filter { catcher -> - catcher.definition.fields.all { definitionField -> - val value = fields[definitionField.key] ?: return@all false - val definition = definitionField.value - if (definition.type != value::class.java) return@all false - - definition.comparator(value) - } - } ?: emptyList() - } - - // must run under [catchersLock] - private fun initCatchers() { - assert(catchersLock.isLocked) - - for (catcher in FusEventCatcher.EP_NAME.extensionList.map { it.getInstance() }) { - val key = catcher.definition.group to catcher.definition.event - catchers.getOrPut(key) { mutableListOf() }.add(catcher) - } - } -} - -private class Listener : StatisticsEventLogListener { - override fun onLogEvent(validatedEvent: LogEvent, rawEventId: String?, rawData: Map?) { - // rawEventId and rawData can't be used - - val group = validatedEvent.group.id - val event = validatedEvent.event.id - val fields = validatedEvent.event.data - - FusEventCatcherService.getInstance().submit(group, event, fields) - } -} - -internal class FusExternalEventLogListenerProvider : ExternalEventLogListenerProviderExtension { - override fun forceLoggingAlwaysEnabled(): Boolean { - return true - } - - override fun getEventLogListener(recorderId: String): StatisticsEventLogListener? { - if (recorderId == "FUS") { - return Listener() - } - else return null - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/package-info.java deleted file mode 100644 index 15c9a1fbf0f8..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/baseEvents/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core.baseEvents; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/ISqliteBackedDatabaseLayer.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/ISqliteBackedDatabaseLayer.kt deleted file mode 100644 index 964ab33def53..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/ISqliteBackedDatabaseLayer.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.intellij.ae.database.core.dbs - -/** - * A layer that uses SQLite to store data. - * - * Be very careful with these APIs! You MUST NOT make any write operations to the database - */ -interface ISqliteBackedDatabaseLayer { - /** - * Table name for the current layer. Use this in your SQL queries - */ - val tableName: String -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/ISqliteExecutor.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/ISqliteExecutor.kt deleted file mode 100644 index 8a1be60adb64..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/ISqliteExecutor.kt +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.dbs - -import org.jetbrains.sqlite.SqliteConnection - -interface ISqliteExecutor { - suspend fun execute(action: suspend (initDb: SqliteConnection) -> T): T? -} - -interface ISqliteInternalExecutor { - suspend fun execute(action: suspend (initDb: SqliteConnection, metadata: SqliteDatabaseMetadata) -> T): T? -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/IUserActivityDatabaseLayer.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/IUserActivityDatabaseLayer.kt deleted file mode 100644 index 09557b93f7b0..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/IUserActivityDatabaseLayer.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.intellij.ae.database.core.dbs - -interface IUserActivityDatabaseLayer \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteDatabaseMetadata.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteDatabaseMetadata.kt deleted file mode 100644 index 1ea26d27d8b5..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteDatabaseMetadata.kt +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.dbs - -import com.intellij.ae.database.core.IdService -import com.intellij.ae.database.core.dbs.migrations.LAST_DB_VERSION -import com.intellij.ae.database.core.dbs.migrations.MIGRATIONS -import com.intellij.openapi.diagnostic.logger -import com.intellij.util.concurrency.ThreadingAssertions -import org.jetbrains.sqlite.ObjectBinderFactory -import org.jetbrains.sqlite.SqliteConnection -import org.jetbrains.sqlite.SqliteException - -private val logger = logger() - -/** - * A class that checks that database is properly initialized, runs migrations and stores metadata information. - * Use it to run something in database once per application run - * - * If you want to add new migration, add text to [MIGRATIONS] array - * - * This class must be initialized on background thread - */ -class SqliteDatabaseMetadata internal constructor(private val connection: SqliteConnection, isNewFile: Boolean) { - /** - * ID from database that represents a pair of IDE ID [IdService.id] - * and machine ID [IdService.machineId] and IDE family [ApplicationInfo] - */ - val ideId: Int - - init { - ThreadingAssertions.assertBackgroundThread() - if (isNewFile) { - logger.info("New database, executing migration") - executeMigrations(0) - } - else { - val version = try { - getVersion() - } - catch (t: SqliteException) { - if (t.message == "[SQLITE_ERROR] SQL error or missing database (no such table: meta)") { - logger.info("File exists, but database seem to be uninitialized") - 0 - } - else { - throw t - } - } - executeMigrations(version) - } - - ideId = initIdeId() - } - - private fun executeMigrations(fromVersion: Int) { - val migrations = MIGRATIONS.subList(maxOf(fromVersion, 0), LAST_DB_VERSION) - if (migrations.isEmpty()) { - return - } - - for (migration in migrations) { - connection.execute(migration) - } - - connection - .prepareStatement("UPDATE meta SET version = (?) WHERE true;", ObjectBinderFactory.create1()) - .apply { binder.bind(LAST_DB_VERSION) } - .executeUpdate() - } - - private fun getVersion(): Int { - return connection.selectInt("SELECT version FROM meta LIMIT 1") ?: -1 - } - - private fun initIdeId(): Int { - val potentialResult = connection.prepareStatement("SELECT id FROM ide WHERE ide_id = (?) AND machine_id = (?) LIMIT 1", ObjectBinderFactory.create2()).use { statement -> - statement.binder.bind(IdService.getInstance().id, IdService.getInstance().machineId) - statement.selectInt() - } - if (potentialResult != null) return potentialResult - - return connection.prepareStatement("insert into ide(ide_id, machine_id, family) values (?, ?, ?) RETURNING id;", ObjectBinderFactory.create3()).use { statement -> - statement.binder.bind(IdService.getInstance().id, IdService.getInstance().machineId, IdService.getInstance().ideCode) - statement.selectInt() ?: error("Null was returned when not expected") - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteLazyInitializedDatabase.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteLazyInitializedDatabase.kt deleted file mode 100644 index 9eaf60c4cb50..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteLazyInitializedDatabase.kt +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@file:Suppress("SqlResolve") -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.intellij.ae.database.core.dbs - -import com.intellij.ae.database.core.IdService -import com.intellij.diagnostic.dumpCoroutines -import com.intellij.openapi.application.ApplicationInfo -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.PathManager -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.components.serviceAsync -import com.intellij.openapi.diagnostic.logger -import com.intellij.util.PlatformUtils -import com.intellij.util.io.createDirectories -import kotlinx.coroutines.* -import org.jetbrains.annotations.VisibleForTesting -import org.jetbrains.sqlite.SqliteConnection -import java.nio.file.Path -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicReference -import kotlin.io.path.exists -import kotlin.io.path.isWritable -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -private val logger = logger() - -private const val MAX_CONNECTION_RETRIES_ALLOWED = 5 - -/** - * This service provides access to an instance of [SqliteConnection] - */ -@Service -class SqliteLazyInitializedDatabase(private val cs: CoroutineScope) : ISqliteExecutor, ISqliteInternalExecutor { - companion object { - internal suspend fun getInstanceAsync() = serviceAsync() - internal fun getInstance() = ApplicationManager.getApplication().service() - } - - private class ConnectionHolder(val connection: SqliteConnection, val metadata: SqliteDatabaseMetadata) - private sealed class State { - data object NotInitialized : State() - class Active(val def: Deferred) : State() - class Cancelling(val def: Deferred, val job: Job) : State() - data object Locked : State() - } - - private val databasePath by lazy { createDatabasePath() } - private val connectionAttempts = AtomicInteger(0) - // private val lastAccessTime = MutableStateFlow(0) - private val myConnection = AtomicReference(State.NotInitialized) - private val actionsBeforeDatabaseDisposal = mutableListOf Unit>() - private val closeDispatcher = Dispatchers.IO.limitedParallelism(1) - - private val loggingAttempt = AtomicInteger(0) - - init { - if (System.getProperty("ae.database.fullLock")?.toBoolean() == true) { - myConnection.set(State.Locked) - } - - cs.launch { - while (true) { - delay(6.minutes) - mainClosingLogic(15.seconds, "every 6 min", false) - } - } - - cs.launch { - try { - awaitCancellation() - } - finally { - withContext(NonCancellable) { - mainClosingLogic(6.seconds, "scope cancellation", true) - } - } - } - } - - private var isFinalClosed = false - private suspend fun CoroutineScope.mainClosingLogic(timeout: Duration, reason: String, isFinal: Boolean) { - val shouldLog = isFinal || loggingAttempt.getAndIncrement() % 10 == 0 - if (isFinalClosed) { - logger.info("Close database for reason \"${reason}\" ignored: already closed.") - } - - if (shouldLog) { - logger.info("Starting saving database (reason: $reason)") - } - try { - withTimeout(timeout) { - withContext(closeDispatcher) { - closeDatabaseImpl(isFinal, shouldLog)?.join() - if (shouldLog) { - logger.info("Saving completed (reason: $reason)") - } - } - } - } - catch (t: TimeoutCancellationException) { - if (PlatformUtils.isPyCharm()) { - // for some reason PyCharm and DataSpell installers don't start `closeDatabaseImpl()` in time, but only a minute later - // this is not critical and no data will be (hopefully) lost, but the fix will be impletented a bit later - logger.warn("Saving timeout (saving reason: $reason)\n${dumpCoroutines(this)}", t) - logger.warn(dumpCoroutines(this)) - } - else { - logger.error("Saving timeout (saving reason: $reason)\n${dumpCoroutines(this)}", t) - } - } - catch (t: Throwable) { - logger.error("Saving failed (saving reason: $reason)\n${dumpCoroutines(this)}", t) - } - finally { - if (isFinal) isFinalClosed = true - } - } - - fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - actionsBeforeDatabaseDisposal.add(action) - } - - @VisibleForTesting - suspend fun closeDatabase() { - coroutineScope { - mainClosingLogic(16.seconds, "in test", true) - } - } - - @Suppress("OPT_IN_USAGE") - private fun closeDatabaseImpl(isFinal: Boolean, shouldLog: Boolean): Job? { - // service scope is dead at this point, need to use GlobalScope - // todo: not true on temp close - fun launchJob(action: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.IO, start = CoroutineStart.LAZY, action) - - while (true) { - when (val state = myConnection.get()) { - is State.NotInitialized -> { - if (!isFinal) return null - return launchJob { - logger.info("Opening connection for final termination.") - val connection = getConn2(this) - if (connection != null) { - logger.info("Connection opened, performing termination.") - close(connection, isFinal = true, shouldLog = shouldLog) - } else { - logger.info("Connection open failure.") - } - } - } - is State.Active -> { - val job = launchJob { - close(state.def.await(), isFinal, shouldLog = shouldLog) - } - val newState = State.Cancelling(state.def, job) - if (myConnection.compareAndSet(state, newState)) { - job.invokeOnCompletion { - check(myConnection.compareAndSet(newState, State.NotInitialized)) - } - return job - } - else { - job.cancel() - } - } - is State.Cancelling -> { - return state.job - } - is State.Locked -> { - return null - } - } - } - } - - private suspend fun doExecuteBeforeConnectionClosed(isFinal: Boolean) { - for (action in actionsBeforeDatabaseDisposal) { - action(isFinal) - } - } - - /** - * Allows executing code with [SqliteConnection] - */ - override suspend fun execute(action: suspend (initDb: SqliteConnection, metadata: SqliteDatabaseMetadata) -> T): T? { - val conn = withContext(Dispatchers.IO) { - getConn2() - } - - if (conn == null) { - return null - } - - return action(conn.connection, conn.metadata) - } - - private suspend fun getConn2(scope: CoroutineScope = cs): ConnectionHolder? { - while (true) { - when (val state = myConnection.get()) { - State.NotInitialized -> { - val connectionHolderDeferred = CompletableDeferred(parent = scope.coroutineContext.job) - if (!myConnection.compareAndSet(state, State.Active(connectionHolderDeferred))) { - connectionHolderDeferred.cancel() - continue - } - val conn = kotlin.runCatching { - val dbPath = databasePath - logger.info("Database path: $dbPath") - val isNewFile = dbPath == null || !dbPath.exists() - val newConnection = SqliteConnection(dbPath, false) - val newMetadata = SqliteDatabaseMetadata(newConnection, isNewFile) - ConnectionHolder(newConnection, newMetadata) - } - if (!connectionHolderDeferred.completeWith(conn)) { - if (conn.isSuccess) { - conn.getOrThrow().connection.close() - } - else { - if (connectionAttempts.getAndIncrement() >= MAX_CONNECTION_RETRIES_ALLOWED) { - myConnection.compareAndSet(state, State.Locked) - } - } - } - return connectionHolderDeferred.await() - } - is State.Active -> { - return state.def.await() - } - is State.Cancelling -> { - // during cancellation there might be calls to DB - return state.def.await() - } - is State.Locked -> { - return null - } - } - } - } - - override suspend fun execute(action: suspend (initDb: SqliteConnection) -> T): T? { - return execute { initDb, metadata -> - action(initDb) - } - } - - private suspend fun close(connectionHolder: ConnectionHolder, isFinal: Boolean, shouldLog: Boolean) { - if (shouldLog) { - logger.info("close start") - } - connectionHolder.connection.use { - doExecuteBeforeConnectionClosed(isFinal) - } - check(connectionHolder.connection.isClosed) - if (shouldLog) { - logger.info("close end") - } - } - - private fun createDatabasePath(): Path? { - val tempPath = System.getProperty("ae.database.path") - if (tempPath != null) { - return Path.of(tempPath) - } - - val majorVersion = ApplicationInfo.getInstance().build.baselineVersion - // todo if settings are migrated, hash is the same - val fileName = "ae_${IdService.getInstance().id}-$majorVersion.db" - val fileMask = "ae_${IdService.getInstance().id}-*.db" - - // Attempt 1: store db file in common folder for all ides - val attempt1 = try { - createDatabasePathStoreInCommonFolder(fileName, fileMask) - } - catch (t: Throwable) { - logger.error("Could not get path in common folder", t) - null - } - if (attempt1 != null) { - return attempt1 - } - - // Attempt 2: store db file in IDE's `config` directory - val attempt2 = createDatabasePathStoreInConfigFolder(fileName, fileMask) - - return attempt2 - } - - private fun createDatabasePathStoreInCommonFolder(fileName: String, mask: String): Path? { - val commonFolder = PathManager.getCommonDataPath() - val intellijFolder = commonFolder.resolve("IntelliJ") - val desiredDatabasePath = intellijFolder.resolve(fileName) - - if (System.getProperty("ae.database.forceConfigFolder")?.toBoolean() == true) { - return null - } - - // folder where the file is located is required to be writable for journal creation - if (intellijFolder.exists() && !intellijFolder.isWritable()) { - logger.warn("Folder $intellijFolder exist, but not writable") - return null - } - - if (!intellijFolder.exists() && !commonFolder.isWritable()) { - logger.warn("Folder $intellijFolder does not exist and $commonFolder is not writable") - return null - } - - if (desiredDatabasePath.exists() && !desiredDatabasePath.isWritable()) { - logger.warn("Db file $desiredDatabasePath exists, but not writable") - return null - } - - if (!intellijFolder.exists()) { - intellijFolder.createDirectories() - } - - return desiredDatabasePath - } - - private fun createDatabasePathStoreInConfigFolder(fileName: String, mask: String): Path { - val configFolder = PathManager.getConfigDir() - - val databasePath = configFolder.resolve(fileName) - - return databasePath - } -} diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteNames.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteNames.kt deleted file mode 100644 index 308dbbf0be7d..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/SqliteNames.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.intellij.ae.database.core.dbs - -object SqliteNames { - const val META_TABLE = "meta" - const val IDE_TABLE = "ide" - const val COUNTER_USER_ACTIVITY_TABLE = "counterUserActivity" -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/CounterUserActivityDatabase.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/CounterUserActivityDatabase.kt deleted file mode 100644 index 405167b3d6d9..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/CounterUserActivityDatabase.kt +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@file:Suppress("SqlResolve") - -package com.intellij.ae.database.core.dbs.counter - -import com.intellij.ae.database.core.IdService -import com.intellij.ae.database.core.activities.DatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.dbs.* -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.components.serviceAsync -import com.intellij.openapi.diagnostic.thisLogger -import kotlinx.coroutines.CoroutineScope -import org.jetbrains.sqlite.ObjectBinderFactory -import org.jetbrains.sqlite.SqliteConnection -import java.time.Instant - -/** - * Database for storing and retrieving counter-based events. - * - * All events are rounded to a minute, seconds and milliseconds are always 0 - */ -@Service -class CounterUserActivityDatabase(cs: CoroutineScope) : ICounterUserActivityDatabase, - IUserActivityDatabaseLayer, - IReadOnlyCounterUserActivityDatabase, - IInternalCounterUserActivityDatabase, - ISqliteExecutor, ISqliteBackedDatabaseLayer { - companion object { - suspend fun getInstanceAsync() = serviceAsync() - fun getInstance() = ApplicationManager.getApplication().service() - } - - private val throttler = CounterUserActivityDatabaseThrottler(cs, this, runBackgroundUpdater = !ApplicationManager.getApplication().isUnitTestMode) - - /** - * Retrieves the activity for a user based on the provided activity ID and time range. - * Answers the question 'How many times did activity happen in given timeframe?' - */ - override suspend fun getActivitySum(activity: DatabaseBackedCounterUserActivity, from: Instant?, until: Instant?): Int { - val nnFrom = InstantUtils.formatForDatabase(from ?: InstantUtils.SomeTimeAgo) - val nnUntil = InstantUtils.formatForDatabase(until ?: InstantUtils.NowButABitLater) - - return execute { connection -> - val getActivityStatement = connection.prepareStatement( - "SELECT sum(diff) FROM counterUserActivity WHERE activity_id = ? AND created_at >= ? AND created_at <= ?", - ObjectBinderFactory.create3() - ) - throttler.commitChanges() - - getActivityStatement.binder.bind(activity.id, nnFrom, nnUntil) - getActivityStatement.selectInt() ?: 0 - } ?: 0 - } - - /** - * Main entry point for submitting new event update - * - * This method doesn't submit to database, but first submits to throttler which - */ - override suspend fun submit(activity: DatabaseBackedCounterUserActivity, diff: Int, eventTime: Instant) { - thisLogger().info("${activity.id} = $diff") - throttler.submit(activity, diff, eventTime) - } - - /** - * Writes event directly to database. Very internal API! - */ - override suspend fun submitDirect(activity: DatabaseBackedCounterUserActivity, diff: Int, instant: Instant, extra: Map?) { - execute { database, metadata -> - val updateActivityStatement = database.prepareStatement( - "INSERT INTO counterUserActivity (activity_id, diff, created_at, ide_id) VALUES (?, ?, ?, ?)", - ObjectBinderFactory.create4() - ) - updateActivityStatement.binder.bind(activity.id, diff, InstantUtils.formatForDatabase(instant), IdService.getInstance().getDatabaseId(metadata)) - updateActivityStatement.executeUpdate() - } - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - SqliteLazyInitializedDatabase.getInstance().executeBeforeConnectionClosed(action) - } - - override suspend fun execute(action: suspend (initDb: SqliteConnection) -> T): T? { - return SqliteLazyInitializedDatabase.getInstanceAsync().execute(action) - } - - private suspend fun execute(action: suspend (initDb: SqliteConnection, metadata: SqliteDatabaseMetadata) -> T): T? { - return SqliteLazyInitializedDatabase.getInstanceAsync().execute(action) - } - - override val tableName: String = "counterUserActivity" -} diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/CounterUserActivityDatabaseThrottler.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/CounterUserActivityDatabaseThrottler.kt deleted file mode 100644 index 35b58c7f081c..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/CounterUserActivityDatabaseThrottler.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.intellij.ae.database.core.dbs.counter - -import com.intellij.ae.database.core.activities.DatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.openapi.diagnostic.logger -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import java.time.Instant -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes - -private val logger = logger() - -private data class CounterUserActivityHits( - val firstOccurance: Instant, - var lastOccurance: Instant, - var count: Int -) - -/** - * Collects events and posts it as a "one big event" to a database to avoid intensive write operations - */ -internal class CounterUserActivityDatabaseThrottler(private val cs: CoroutineScope, - private val database: IInternalCounterUserActivityDatabase, - private val updatePause: Duration = 2.minutes, - runBackgroundUpdater: Boolean = true) { - private val events = HashMap() - private val eventsLock = Mutex() - - init { - if (runBackgroundUpdater) { - cs.launch { - while (isActive) { - logger.trace("Updater was (re)started") - delay(updatePause) - commitChanges() - logger.trace("Updater finished") - } - } - } - - database.executeBeforeConnectionClosed { isFinal -> - if (isFinal) { - commitChanges() - } - } - } - - suspend fun submit(activity: DatabaseBackedCounterUserActivity, newValue: Int, eventTime: Instant = InstantUtils.Now) { - eventsLock.withLock { - val ev = events.getOrElse(activity) { - CounterUserActivityHits(eventTime, eventTime, 0) - } - - ev.lastOccurance = eventTime - ev.count += newValue - - events[activity] = ev - } - } - - suspend fun commitChanges() { - eventsLock.withLock { - if (events.isNotEmpty()) { - events - .filter { it.value.count != 0 } - .apply { logger.info("Started committing changes ($size)") } - .forEach { - database.submitDirect(it.key, it.value.count, it.value.lastOccurance, null) - } - events.clear() - } - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/ICounterUserActivityDatabase.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/ICounterUserActivityDatabase.kt deleted file mode 100644 index 8f2089fa3de2..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/ICounterUserActivityDatabase.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.intellij.ae.database.core.dbs.counter - -import com.intellij.ae.database.core.activities.DatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.utils.InstantUtils -import java.time.Instant - -interface ICounterUserActivityDatabase { - suspend fun submit(activity: DatabaseBackedCounterUserActivity, diff: Int, eventTime: Instant = InstantUtils.Now) -} - -internal interface IInternalCounterUserActivityDatabase { - suspend fun submitDirect(activity: DatabaseBackedCounterUserActivity, diff: Int, instant: Instant, extra: Map? = null) - fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) -} - -interface IReadOnlyCounterUserActivityDatabase { - suspend fun getActivitySum(activity: DatabaseBackedCounterUserActivity, from: Instant?, until: Instant?): Int -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/package-info.java deleted file mode 100644 index 987bbe1217ca..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/counter/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core.dbs.counter; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/migrations.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/migrations.kt deleted file mode 100644 index 9f78c01dc219..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/migrations.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.intellij.ae.database.core.dbs.migrations - -val MIGRATIONS = listOf(MIGRATION_V1, MIGRATION_V2) -val LAST_DB_VERSION = MIGRATIONS.size diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/package-info.java deleted file mode 100644 index 12c529259172..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core.dbs.migrations; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/v1_InitialSchema.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/v1_InitialSchema.kt deleted file mode 100644 index 6a9d2e81aa58..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/v1_InitialSchema.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.intellij.ae.database.core.dbs.migrations - -import org.intellij.lang.annotations.Language - -@Language("SQLite") -internal val MIGRATION_V1 = """ -CREATE TABLE "meta" ( - version INTEGER NOT NULL -); - -CREATE TABLE "ide" ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - machine_id TEXT NOT NULL, - ide_id TEXT NOT NULL, - family TEXT NOT NULL -); - -CREATE TABLE "counterUserActivity" ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - activity_id TEXT NOT NULL, - ide_id TEXT NOT NULL, - created_at TEXT NOT NULL, - diff INTEGER NOT NULL, - - extra TEXT, - - FOREIGN KEY (ide_id) REFERENCES ide(id) -); - -CREATE TABLE "timespanUserActivity" ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - activity_id TEXT NOT NULL, - ide_id TEXT NOT NULL, - started_at TEXT NOT NULL, - ended_at TEXT NOT NULL, - - extra TEXT, - - FOREIGN KEY (ide_id) REFERENCES ide(id) -); - -INSERT INTO meta (version) VALUES (1); -""" \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/v2_EventsFinished.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/v2_EventsFinished.kt deleted file mode 100644 index ee332b7d7cdb..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/migrations/v2_EventsFinished.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.intellij.ae.database.core.dbs.migrations - -import org.intellij.lang.annotations.Language - -@Language("SQLite") -internal val MIGRATION_V2 = """ -ALTER TABLE "timespanUserActivity" ADD COLUMN is_finished INTEGER DEFAULT 0; -""" diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/package-info.java deleted file mode 100644 index 064c1f4a2618..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core.dbs; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/EventDescriptor.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/EventDescriptor.kt deleted file mode 100644 index 0205421690fb..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/EventDescriptor.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.intellij.ae.database.core.dbs.timespan - -import com.intellij.ae.database.core.activities.DatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.activities.toKey -import java.time.Instant - -data class EventDescriptor private constructor ( - val activity: DatabaseBackedTimeSpanUserActivity, - val id: String, - val canBeStale: Boolean, - val isPeriodic: Boolean, - val startedAt: Instant, - var endAt: Instant? = null, - var databaseId: Int? = null, - val extra: Map? = null, -) { - companion object { - internal fun manual( - activity: DatabaseBackedTimeSpanUserActivity, - id: String, - canBeStale: Boolean, - startedAt: Instant, - extra: Map?, - ): EventDescriptor = EventDescriptor( - activity, - id, - canBeStale, - isPeriodic = false, - startedAt = startedAt, - endAt = null, - databaseId = null, - extra = extra, - ) - - internal fun periodic( - activity: DatabaseBackedTimeSpanUserActivity, - id: String, - startedAt: Instant, - endAt: Instant, - canBeStale: Boolean, - extra: Map?, - ): EventDescriptor = EventDescriptor( - activity, - id, - canBeStale = canBeStale, - isPeriodic = true, - startedAt = startedAt, - endAt = endAt, - databaseId = null, - extra = extra, - ) - } -} - -internal fun EventDescriptor.toKey(): String = this.activity.toKey(id) \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/TimeSpanUserActivityDatabase.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/TimeSpanUserActivityDatabase.kt deleted file mode 100644 index aed84056cee8..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/TimeSpanUserActivityDatabase.kt +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@file:Suppress("SqlResolve") - -package com.intellij.ae.database.core.dbs.timespan - -import com.intellij.ae.database.core.IdService -import com.intellij.ae.database.core.activities.DatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.activities.WritableDatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.dbs.* -import com.intellij.ae.database.core.formatString -import com.intellij.ae.database.core.models.TimeSpan -import com.intellij.ae.database.core.utils.BooleanUtils -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.serviceAsync -import com.intellij.openapi.diagnostic.logger -import kotlinx.coroutines.CoroutineScope -import org.jetbrains.annotations.TestOnly -import org.jetbrains.sqlite.ObjectBinderFactory -import org.jetbrains.sqlite.SqliteConnection -import java.time.Instant - -enum class TimeSpanUserActivityDatabaseManualKind { - Start, - End -} - -private val logger = logger() - -@Service -class TimeSpanUserActivityDatabase(cs: CoroutineScope) : IUserActivityDatabaseLayer, - IReadOnlyTimeSpanUserActivityDatabase, - ITimeSpanUserActivityDatabase, - IInternalTimeSpanUserActivityDatabase, - ISqliteBackedDatabaseLayer, - ISqliteExecutor { - companion object { - internal suspend fun getInstanceAsync() = serviceAsync() - } - - private val throttler = TimeSpanUserActivityDatabaseThrottler(cs, this) - - @TestOnly - fun cancelBackgroundUpdate() { - throttler.cancelBackgroundUpdate() - } - - override suspend fun getLongestActivity(activity: DatabaseBackedTimeSpanUserActivity, from: Instant?, until: Instant?): TimeSpan? { - return execute { database -> - throttler.commitChanges(false) - - val longestActivityStatement = database.prepareStatement( - """SELECT activity_id, started_at, ended_at FROM timespanUserActivity - |WHERE activity_id = ? AND (julianday(started_at) >= julianday(?) AND julianday(?) <= julianday(ended_at)) - |ORDER BY (julianday(ended_at) - julianday(started_at)) DESC LIMIT ?""".trimMargin(), - ObjectBinderFactory.create4() - ) - - longestActivityStatement.binder.bind(activity.id, InstantUtils.formatForDatabase(from ?: InstantUtils.SomeTimeAgo), - InstantUtils.formatForDatabase(until ?: InstantUtils.NowButABitLater), 1) - - longestActivityStatement.executeQuery().let { - val activityId = it.getString(0) ?: error("Required column activityId was not found") - val startedAt = it.getString(1) ?: error("Required column startedAt was not found") - val endedAt = it.getString(2) ?: error("Required column endedAt was not found") - TimeSpan(activity, activityId, InstantUtils.fromString(startedAt), InstantUtils.fromString(endedAt)) - } - } - } - - /** - * Some events may know when they end, so it's more preferable to use this method - * - * @param activity - * @param kind - * @param canBeStale if true, database will end event when coroutine scope (=application) dies - */ - override suspend fun submitManual(activity: DatabaseBackedTimeSpanUserActivity, - id: String, - kind: TimeSpanUserActivityDatabaseManualKind, - canBeStale: Boolean, - extra: Map?, - moment: Instant?) { - throttler.submitManual(activity, id, kind, canBeStale, moment, extra) - } - - override suspend fun endAllEvents(): Int { - return execute { db -> - val stmt = db.prepareStatement( - """ - UPDATE timespanUserActivity - SET ended_at = ?, - is_finished = ? - """.trimIndent(), - ObjectBinderFactory.create2(), - ) - - val dbEndedAt = InstantUtils.formatForDatabase(InstantUtils.Now) - val dbIsFinished = BooleanUtils.formatForDatabase(true) - stmt.binder.bind(dbEndedAt, dbIsFinished) - stmt.executeUpdate() - return@execute db.affectedRows() - } ?: 0 - } - - override suspend fun removeEvent(activity: DatabaseBackedTimeSpanUserActivity): Int { - return execute { db -> - val stmt = db.prepareStatement( - "DELETE FROM timespanUserActivity WHERE activity_id = ?", - ObjectBinderFactory.create1(), - ) - stmt.binder.bind(activity.id) - stmt.executeUpdate() - return@execute db.affectedRows() - } ?: 0 - } - - override suspend fun endEventInternal( - databaseId: Int?, - activity: DatabaseBackedTimeSpanUserActivity, - startedAt: Instant, - endedAt: Instant, - isFinished: Boolean, - extra: Map?, - ): Int { - val dbStartedAt = InstantUtils.formatForDatabase(startedAt) - val dbEndedAt = InstantUtils.formatForDatabase(endedAt) - val dbIsFinished = BooleanUtils.formatForDatabase(isFinished) - - return execute { database, metadata -> - if (databaseId != null) { - val endEventUpdateStatement = database.prepareStatement( - """ - UPDATE timespanUserActivity - SET ended_at = ?, - is_finished = ? - WHERE id = ? - """.trimIndent(), - ObjectBinderFactory.create3(), - ) - endEventUpdateStatement.binder.bind(dbEndedAt, dbIsFinished, databaseId) - endEventUpdateStatement.executeUpdate() - // Return the current ID - databaseId - } - else { - val endEventInsertStatement = database.prepareStatement( - """ - INSERT INTO timespanUserActivity ( - activity_id, - ide_id, - started_at, - ended_at, - is_finished, - extra - ) - VALUES (?, ?, ?, ?, ?, ?) - RETURNING id; - """.trimIndent(), - ObjectBinderFactory.create6(), - ) - - val extraString = extra?.let { formatString(it) } - endEventInsertStatement.binder.bind( - activity.id, - IdService.getInstance().getDatabaseId(metadata), - dbStartedAt, - dbEndedAt, - dbIsFinished, - extraString, - ) - - // Return inserted ID - endEventInsertStatement - .executeQuery() - .getInt(0) - } - } ?: error("Could not execute because of null from database") - } - - /** - * Use this method if you don't know when your event will end. Feel free to call this method as much as you want. - * Event is considered to be finished if no updates were submitted in +-2 minutes - * - * @param activity - * @param id - * @param extra the first submitted [extra] will be used - */ - override suspend fun submitPeriodicEvent(activity: WritableDatabaseBackedTimeSpanUserActivity, id: String, extra: Map?) { - throttler.submitPeriodic(activity, id, activity.canBeStale, extra) - } - - override suspend fun cancel(activity: WritableDatabaseBackedTimeSpanUserActivity, id: String) { - - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - SqliteLazyInitializedDatabase.getInstance().executeBeforeConnectionClosed(action) - } - - override suspend fun execute(action: suspend (initDb: SqliteConnection) -> T): T? { - return SqliteLazyInitializedDatabase.getInstanceAsync().execute(action) - } - - private suspend fun execute(action: suspend (initDb: SqliteConnection, metadata: SqliteDatabaseMetadata) -> T): T? { - return SqliteLazyInitializedDatabase.getInstanceAsync().execute(action) - } - - override val tableName: String = "timespanUserActivity" -} - -interface IReadOnlyTimeSpanUserActivityDatabase { - suspend fun getLongestActivity(activity: DatabaseBackedTimeSpanUserActivity, from: Instant?, until: Instant?): TimeSpan? -} - -interface ITimeSpanUserActivityDatabase : IReadOnlyTimeSpanUserActivityDatabase { - suspend fun submitManual(activity: DatabaseBackedTimeSpanUserActivity, - id: String, - kind: TimeSpanUserActivityDatabaseManualKind, - canBeStale: Boolean, - extra: Map? = null, - moment: Instant?) - - suspend fun submitPeriodicEvent(activity: WritableDatabaseBackedTimeSpanUserActivity, id: String, extra: Map? = null) - suspend fun cancel(activity: WritableDatabaseBackedTimeSpanUserActivity, id: String) -} - -internal interface IInternalTimeSpanUserActivityDatabase { - suspend fun endAllEvents(): Int - - suspend fun removeEvent(activity: DatabaseBackedTimeSpanUserActivity): Int - - suspend fun endEventInternal( - databaseId: Int?, - activity: DatabaseBackedTimeSpanUserActivity, - startedAt: Instant, - endedAt: Instant, - isFinished: Boolean, - extra: Map? = null, - ): Int - - fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/TimeSpanUserActivityDatabaseThrottler.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/TimeSpanUserActivityDatabaseThrottler.kt deleted file mode 100644 index e1c572f49151..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/TimeSpanUserActivityDatabaseThrottler.kt +++ /dev/null @@ -1,207 +0,0 @@ -package com.intellij.ae.database.core.dbs.timespan - -import com.intellij.ae.database.core.activities.DatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.activities.toKey -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.openapi.diagnostic.logger -import kotlinx.coroutines.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.jetbrains.annotations.TestOnly -import java.time.Duration -import java.time.Instant -import kotlin.time.Duration.Companion.minutes - -private val logger = logger() - -internal class TimeSpanUserActivityDatabaseThrottler(cs: CoroutineScope, - private val database: IInternalTimeSpanUserActivityDatabase, - private val updatePause: kotlin.time.Duration = 4.minutes, - private val eventTtl: Duration = Duration.ofMinutes(2L), - runBackgroundUpdater: Boolean = true) { - - private val events = HashMap() - private val eventsLock = Mutex() - private val updaterJob: Job? = if (runBackgroundUpdater) startBackgroundUpdater(cs) else null - - init { - /*cs.launch { - submitDanglingEvents() - }*/ - - database.executeBeforeConnectionClosed { isFinal -> - if (isFinal) { - commitChanges(true) - commitStaleEvents() - } - } - } - - private fun startBackgroundUpdater(cs: CoroutineScope): Job { - return cs.launch { - while (isActive) { - delay(updatePause) - commitChanges(false) - } - } - } - - @TestOnly - fun cancelBackgroundUpdate() { - updaterJob?.cancel("in test") - } - - private suspend fun submitDanglingEvents() { - eventsLock.withLock { - val affected = database.endAllEvents() - logger.warn("Submitted ${affected} dangling events") - } - } - - /** - * @return null if submission is incorrect, activity start Instant if kind=TimeSpanUserActivityDatabaseManualKind.Start, activity end Instant if kind=TimeSpanUserActivityDatabaseManualKind.End - */ - suspend fun submitManual(activity: DatabaseBackedTimeSpanUserActivity, - id: String, - kind: TimeSpanUserActivityDatabaseManualKind, - canBeStale: Boolean, - moment: Instant?, - extra: Map?): Instant? { - return eventsLock.withLock { - when (kind) { - TimeSpanUserActivityDatabaseManualKind.Start -> { - if (events.containsKey(activity.toKey(id))) { - logger.warn("Already logged ${activity.toKey(id)}, it won't be overwritten") - null - } - else { - logger.info("Starting activity ${activity.id} with id $id") - val theMoment = moment ?: InstantUtils.Now - events[activity.toKey(id)] = EventDescriptor.manual(activity, id, canBeStale, theMoment, extra) - theMoment - } - } - TimeSpanUserActivityDatabaseManualKind.End -> { - val event = events[activity.toKey(id)] - if (event == null) { - logger.info("Tried to end activity ${activity.toKey(id)} that wasn't started") - null - } - else { - logger.info("Ending activity ${activity.id} with id $id") - val theMoment = moment ?: InstantUtils.Now - endEvent(event, theMoment) - theMoment - } - } - } - } - } - - suspend fun cancel(activity: DatabaseBackedTimeSpanUserActivity, id: String) { - eventsLock.withLock { - val activityId = activity.toKey(id) - - if (database.removeEvent(activity) == 0) { - logger.info("Attempt to remove non-existent activity $activityId") - } - - if (events.remove(activityId) == null) { - logger.info("Tried to cancel activity ${activity.toKey(id)} that wasn't started") - } - } - } - - suspend fun submitPeriodic( - activity: DatabaseBackedTimeSpanUserActivity, - id: String, - canBeStale: Boolean, - extra: Map? - ): Instant? { - return eventsLock.withLock { - val activityId = activity.toKey(id) - val event = events[activityId] - val now = InstantUtils.Now - - if (event?.isPeriodic == false) { - logger.warn("Tried to log activity (${activityId}) as periodic, but it was already submitted as manual therefore it won't be saved") - return@withLock null - } - - val value = event ?: EventDescriptor.periodic(activity, id, now, now, canBeStale, extra) - value.endAt = now - - events[activityId] = value - return@withLock value.startedAt - } - } - - // should run under [eventsLock] - private suspend fun endEvent(eventDescriptor: EventDescriptor, endedAtInit: Instant, isFinished: Boolean = true) { - val endedAt = if (eventDescriptor.startedAt == endedAtInit) { - // We assume that all events lasted at least for a bit - endedAtInit + Duration.ofSeconds(eventTtl.seconds / 2) - } - else { - endedAtInit - } - - // Remove event from map if it's finished - if (isFinished && events.remove(eventDescriptor.toKey()) == null) { - logger.warn("Tried to endEvent ${eventDescriptor.toKey()} which was already finished") - } - - if (eventDescriptor.startedAt > endedAt) { - logger.warn("Event ${eventDescriptor.toKey()} started after it ended. It would be discarded") - events.remove(eventDescriptor.toKey()) - return - } - - eventDescriptor.databaseId = database.endEventInternal( - eventDescriptor.databaseId, - eventDescriptor.activity, - eventDescriptor.startedAt, - endedAt, - isFinished, - eventDescriptor.extra, - ) - } - - private suspend fun commitStaleEvents() { - eventsLock.withLock { - val values = ArrayList(events.values) - for (event in values) { - if (!event.canBeStale && event.endAt == null) { - logger.warn("Event ${event.toKey()} wasn't marked as canBeStale, but the end wasn't reported. It will be still saved anyway") - } - else { - logger.info("Submitting stale event ${event.toKey()}") - } - endEvent(event, InstantUtils.Now) - } - } - } - - internal suspend fun commitChanges(isFinal: Boolean) { - eventsLock.withLock { - val values = ArrayList(events.values) - for (descriptor in values) { - val now = InstantUtils.Now - - if (descriptor.isPeriodic) { - val threshold = now - eventTtl - val endAt = descriptor.endAt - - if (endAt == null) { - logger.warn("Attempt to commit periodic event (${descriptor.toKey()}) with no end time, skipping") - continue - } - - endEvent(descriptor, endAt, isFinished = endAt < threshold || isFinal) - } else { - endEvent(descriptor, now, isFinished = false) - } - } - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/package-info.java deleted file mode 100644 index 329ffa8afba2..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/dbs/timespan/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core.dbs.timespan; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/eventUtils.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/eventUtils.kt deleted file mode 100644 index 03bf32979f46..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/eventUtils.kt +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core - -import com.intellij.ae.database.core.activities.UserActivity -import com.intellij.idea.AppMode -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.diagnostic.logger -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import org.jetbrains.sqlite.SqliteResultSet - -object AEEventUtils - -/** - * Wrap the call to your update() function in user activity into this method, - * so activity won't be updated when not needed (right now – in unit test mode) - * - * @param activity user activity that will be updated - * @param action code that updates activity, is a suspend lambda. [activity] is passed into lambda - */ -inline fun CoroutineScope.runUpdateEvent(activity: T, crossinline action: suspend (T) -> Unit) { - if (ApplicationManager.getApplication().isUnitTestMode) return - if (AppMode.isRemoteDevHost()) return // sorry, no remote dev for now - launch { - try { - action(activity) - } - catch (t: CancellationException) { - throw t - } - catch (t: Throwable) { - logger().error(t) - } - } -} - -internal fun formatString(params: Map): String { - return buildString { - append("{") - - val totalSize = params.size - for (param in params.asIterable().withIndex()) { - escape(param.value.key, this) - append(": ") - escape(param.value.value, this) - if (totalSize-1 != param.index) { - append(", ") - } - } - append("}") - } -} - -fun createMap(getKey: (SqliteResultSet) -> K0, getValue: (SqliteResultSet) -> V0, - validate: (K0, V0) -> Boolean, - transformKey: (K0) -> K, transformValue: (V0) -> V, - validateTransformed: (K, V) -> Boolean, - res: SqliteResultSet): Map { - val map = mutableMapOf() - while (res.next()) { - val k0 = getKey(res) - val v0 = getValue(res) - if (!validate(k0, v0)) continue - - val k = transformKey(k0) - val v = transformValue(v0) - if (!validateTransformed(k, v)) continue - - map[k] = v - } - - return map -} - -private val REPLACEMENT_CHARS = arrayOfNulls(128).apply { - for (i in 0..31) { - this[i] = String.format("\\u%04x", i) - } - this['"'.code] = "\\\"" - this['\\'.code] = "\\\\" - this['\t'.code] = "\\t" - this['\b'.code] = "\\b" - this['\n'.code] = "\\n" - this['\r'.code] = "\\r" - this['\u000c'.code] = "\\f" -} - -// copy-paste of [org.jetbrains.io.JsonUtil], not to depend on platform-impl -private fun escape(value: CharSequence, sb: StringBuilder) { - val length = value.length - sb.ensureCapacity(sb.length + length + 2) - sb.append('"') - var last = 0 - for (i in 0 until length) { - val c = value[i] - var replacement: String? - if (c.code < 128) { - replacement = REPLACEMENT_CHARS.get(c.code) - if (replacement == null) { - continue - } - } - else if (c == '\u2028') { - replacement = "\\u2028" - } - else if (c == '\u2029') { - replacement = "\\u2029" - } - else { - continue - } - if (last < i) { - sb.append(value, last, i) - } - sb.append(replacement) - last = i + 1 - } - if (last < length) { - sb.append(value, last, length) - } - sb.append('"') -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/models/TimeSpan.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/models/TimeSpan.kt deleted file mode 100644 index 4a8af98d86ac..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/models/TimeSpan.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.intellij.ae.database.core.models - -import com.intellij.ae.database.core.activities.DatabaseBackedTimeSpanUserActivity -import java.time.Instant - -data class TimeSpan( - val activity: DatabaseBackedTimeSpanUserActivity, - val id: String, - val start: Instant, - val end: Instant -) diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/models/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/models/package-info.java deleted file mode 100644 index b44d873dd691..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/models/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core.models; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/package-info.java deleted file mode 100644 index d8cbb2213be4..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/BooleanUtils.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/BooleanUtils.kt deleted file mode 100644 index 11f8d7b7dd7d..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/BooleanUtils.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.intellij.ae.database.core.utils - -object BooleanUtils { - fun formatForDatabase(v: Boolean): Int { - return if (v) { 1 } else { 0 } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/InstantUtils.kt b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/InstantUtils.kt deleted file mode 100644 index 1a06bf1cd827..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/InstantUtils.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.intellij.ae.database.core.utils - -import java.time.Instant -import java.time.temporal.ChronoUnit - -object InstantUtils { - val SomeTimeAgo: Instant = Instant.ofEpochSecond(0) - val Now: Instant get() = Instant.now() - val NowButABitLater: Instant get() = Now.plusSeconds(20 * 60) - val StartOfDay: Instant get() = Now.truncatedTo(ChronoUnit.DAYS) - val WeekAgo: Instant get() = Now.minus(7, ChronoUnit.DAYS) - val WeekAgoStartOfDay: Instant get() = WeekAgo.truncatedTo(ChronoUnit.DAYS) - - fun formatForDatabase(instant: Instant): String = instant.truncatedTo(ChronoUnit.SECONDS).toString() - - fun fromString(str: String): Instant = Instant.parse(str) -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/package-info.java b/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/package-info.java deleted file mode 100644 index dc2469a3000d..000000000000 --- a/plugins/feature-usage-database/core/src/com/intellij/ae/database/core/utils/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.ae.database.core.utils; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/AETestCoroutineScope.kt b/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/AETestCoroutineScope.kt deleted file mode 100644 index 08ed9d5b0340..000000000000 --- a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/AETestCoroutineScope.kt +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.tests.dbs - -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import kotlinx.coroutines.CoroutineScope - -@Service -class AETestCoroutineScope(private val cs: CoroutineScope) { - companion object { - fun getScope() = service().cs - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/SqliteLazyInitializedDatabaseTest.kt b/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/SqliteLazyInitializedDatabaseTest.kt deleted file mode 100644 index 7afd03212f57..000000000000 --- a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/SqliteLazyInitializedDatabaseTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.tests.dbs - -import com.intellij.ae.database.core.IdService -import com.intellij.ae.database.core.dbs.SqliteLazyInitializedDatabase -import com.intellij.openapi.util.io.FileUtil -import com.intellij.testFramework.common.timeoutRunBlocking -import com.intellij.testFramework.fixtures.BasePlatformTestCase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.jetbrains.sqlite.EmptyBinder -import org.jetbrains.sqlite.ObjectBinderFactory -import org.junit.Assert - -class SqliteLazyInitializedDatabaseTest : BasePlatformTestCase() { - /** - * Read/write inside once sql connection - */ - fun testBasicReadWrite(): Unit = timeoutRunBlocking { - System.setProperty("ae.database.path", FileUtil.generateRandomTemporaryPath("ae", ".db").absolutePath) - val db1 = SqliteLazyInitializedDatabase(this) - withContext(Dispatchers.Default) { - db1.execute { initDb -> - val writeStatement = initDb.prepareStatement("insert into ide(ide_id, machine_id, family) values (?, ?, ?)", - ObjectBinderFactory.create3()).apply { - binder.bind("test1", "test2", "test3") - } - writeStatement.executeUpdate() - - val readStatement = initDb.prepareStatement("SELECT * FROM ide WHERE ide_id IS (?)", ObjectBinderFactory.create1()).apply { - binder.bind("test1") - } - val resultSet = readStatement.executeQuery() - var hits = 0 - while (resultSet.next()) { - ++hits - // column zero is database id, not important here - val machineId = resultSet.getString(1) - val ideId = resultSet.getString(2) - val family = resultSet.getString(3) - Assert.assertArrayEquals(arrayOf("test1", "test2", "test3"), arrayOf(ideId, machineId, family)) - } - Assert.assertEquals(1, hits) - } - db1.closeDatabase() - } - } - - /** - * Write to DB, close it, open, read - */ - fun testReadWriteAfterClosing(): Unit = timeoutRunBlocking { - System.setProperty("ae.database.path", FileUtil.generateRandomTemporaryPath("ae", ".db").absolutePath) - - withContext(Dispatchers.Default) { - val db1 = SqliteLazyInitializedDatabase(this) - db1.execute { initDb -> - val writeStatement = initDb.prepareStatement("insert into ide(ide_id, machine_id, family) values (?, ?, ?)", - ObjectBinderFactory.create3()).apply { - binder.bind("test1", "test2", "test3") - } - writeStatement.executeUpdate() - } - db1.closeDatabase() - - val db2 = SqliteLazyInitializedDatabase(this) - db2.execute { initDb -> - val readStatement = initDb.prepareStatement("SELECT * FROM ide WHERE ide_id IS (?)", ObjectBinderFactory.create1()).apply { - binder.bind("test1") - } - val resultSet = readStatement.executeQuery() - var hits = 0 - while (resultSet.next()) { - ++hits - // column zero is database id, not important here - val machineId = resultSet.getString(1) - val ideId = resultSet.getString(2) - val family = resultSet.getString(3) - Assert.assertArrayEquals(arrayOf("test1", "test2", "test3"), arrayOf(ideId, machineId, family)) - } - Assert.assertEquals(1, hits) - } - db2.closeDatabase() - } - } - - fun testProperMetadata(): Unit = timeoutRunBlocking { - System.setProperty("ae.database.path", FileUtil.generateRandomTemporaryPath("ae", ".db").absolutePath) - - withContext(Dispatchers.Default) { - val db1 = SqliteLazyInitializedDatabase(this) - db1.execute { initDb, metadata -> - Assert.assertEquals(1, metadata.ideId) - - val entries = initDb.prepareStatement("SELECT * FROM ide", EmptyBinder).executeQuery() - var hits = 0 - while (entries.next()) { - ++hits - // column zero is database id, not important here - val machineId = entries.getString(1) - val ideId = entries.getString(2) - val family = entries.getString(3) - Assert.assertArrayEquals(arrayOf(IdService.getInstance().id, IdService.getInstance().machineId, IdService.getInstance().ideCode), arrayOf(ideId, machineId, family)) - } - Assert.assertEquals(1, hits) - } - db1.closeDatabase() - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/counter/CounterUserActivityDatabaseTest.kt b/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/counter/CounterUserActivityDatabaseTest.kt deleted file mode 100644 index 248651931074..000000000000 --- a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/counter/CounterUserActivityDatabaseTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.tests.dbs.counter - -import com.intellij.ae.database.core.activities.DatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.dbs.counter.CounterUserActivityDatabase -import com.intellij.ae.database.core.tests.dbs.runDatabaseLayerTest -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.testFramework.fixtures.BasePlatformTestCase -import kotlinx.coroutines.CoroutineScope -import org.junit.Assert - -class CounterUserActivityDatabaseTest : BasePlatformTestCase() { - private val databaseFactory = { cs: CoroutineScope -> - CounterUserActivityDatabase(cs) - } - - fun testGetActivitySum() = runDatabaseLayerTest(databaseFactory) { it -> - val item1 = 13 - val item2 = 1989 - val result = item1+item2 - - val activity = createActivity() - it.submitDirect(activity, item1, InstantUtils.Now, null) - it.submitDirect(activity, item2, InstantUtils.Now, null) - - val fromDb = it.getActivitySum(activity, InstantUtils.SomeTimeAgo, InstantUtils.NowButABitLater) - - Assert.assertEquals(result, fromDb) - } - - private fun createActivity() = object : DatabaseBackedCounterUserActivity() { - override val id = "testActivity" - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/counter/CounterUserActivityDatabaseThrottlerTest.kt b/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/counter/CounterUserActivityDatabaseThrottlerTest.kt deleted file mode 100644 index 801d1d3a21ea..000000000000 --- a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/counter/CounterUserActivityDatabaseThrottlerTest.kt +++ /dev/null @@ -1,196 +0,0 @@ -package com.intellij.ae.database.core.tests.dbs.counter - -import com.intellij.ae.database.core.activities.DatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.dbs.counter.CounterUserActivityDatabaseThrottler -import com.intellij.ae.database.core.dbs.counter.IInternalCounterUserActivityDatabase -import com.intellij.testFramework.common.timeoutRunBlocking -import com.intellij.testFramework.fixtures.BasePlatformTestCase -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import org.junit.Assert -import java.time.Duration -import java.time.Instant -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -class CounterUserActivityDatabaseThrottlerTest : BasePlatformTestCase() { - private fun runCounterUserActivityTest(flushImmediately: Boolean, submit: suspend (CounterUserActivityDatabaseThrottler, DatabaseBackedCounterUserActivity) -> Unit, asserts: (diff: Int, instant: Instant, lock: Mutex) -> Unit) { - val myActivity = object : DatabaseBackedCounterUserActivity() { - override val id: String get() = "testActivity" - } - - val lock = Mutex() - - val onDatabaseDeath = mutableListOf Unit>() - val fakeDatabase = object : IInternalCounterUserActivityDatabase { - override suspend fun submitDirect(activity: DatabaseBackedCounterUserActivity, - diff: Int, - instant: Instant, - extra: Map?) { - Assert.assertTrue(myActivity === activity) - asserts(diff, instant, lock) - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - onDatabaseDeath.add(action) - } - } - - timeoutRunBlocking { - val throttlerCoroutine = launch { - CounterUserActivityDatabaseThrottler(this, fakeDatabase, 2.seconds).apply { - submit(this, myActivity) - /* - // TODO: if this is uncommented, tests work faster, but different from how it works in prod - if (flushImmediately) { - commitChanges() - } - */ - } - } - - val submissionTime = Instant.now() - // The first lock() call locks, the second one awaits - lock.lock() - lock.lock() - println("Took ${Duration.between(submissionTime, Instant.now()).seconds} seconds to submit event") - for (task in onDatabaseDeath) { - task(true) - } - throttlerCoroutine.cancel() - } - } - - fun testOneEvent() { - val newValue = 13 - val submit: suspend (CounterUserActivityDatabaseThrottler, DatabaseBackedCounterUserActivity) -> Unit = { thr, act -> - thr.submit(act, newValue) - } - val verify: (Int, Instant, Mutex) -> Unit = { diff, instant, lock -> - Assert.assertEquals(newValue, diff) - lock.unlock() - } - runCounterUserActivityTest(true, submit, verify) - } - - fun testSeveralEvents() { - val firstValue = 13 - val secondValue = 1989 - val result = firstValue+secondValue - - val submit: suspend (CounterUserActivityDatabaseThrottler, DatabaseBackedCounterUserActivity) -> Unit = { thr, act -> - thr.submit(act, firstValue) - thr.submit(act, secondValue) - } - val verify: (Int, Instant, Mutex) -> Unit = { diff, instant, lock -> - Assert.assertEquals(result, diff) - lock.unlock() - } - runCounterUserActivityTest(true, submit, verify) - } - - fun testSeveralEventsNegative() { - val firstValue = 13 - val secondValue = -1989 - val result = firstValue+secondValue - - val submit: suspend (CounterUserActivityDatabaseThrottler, DatabaseBackedCounterUserActivity) -> Unit = { thr, act -> - thr.submit(act, firstValue) - thr.submit(act, secondValue) - } - val verify: (Int, Instant, Mutex) -> Unit = { diff, instant, lock -> - Assert.assertEquals(result, diff) - lock.unlock() - } - runCounterUserActivityTest(true, submit, verify) - } - - fun testSeveralValuesSeparate() { - val firstValue = 13 - val secondValue = -1989 - - val submit: suspend (CounterUserActivityDatabaseThrottler, DatabaseBackedCounterUserActivity) -> Unit = { thr, act -> - thr.submit(act, firstValue) - coroutineScope { - launch { - delay(2.seconds) - thr.submit(act, secondValue) - } - } - } - var hit = 0 - val verify: (Int, Instant, Mutex) -> Unit = { diff, instant, lock -> - when (++hit) { - 1 -> { - Assert.assertEquals(firstValue, diff) - } - 2 -> { - Assert.assertEquals(secondValue, diff) - lock.unlock() - } - else -> { - fail("Got hit $hit, expected >=2") - } - } - } - runCounterUserActivityTest(true, submit, verify) - } - - fun testShowdown() { - val myActivity1 = object : DatabaseBackedCounterUserActivity() { - override val id: String get() = "testActivity1" - } - val myActivity2 = object : DatabaseBackedCounterUserActivity() { - override val id: String get() = "testActivity2" - } - - val onDatabaseDeath = mutableListOf Unit>() - - val expected = mapOf( - myActivity1.id to 13, - myActivity2.id to 13*2 - ) - - val endedEvents = mutableMapOf() - - timeoutRunBlocking { - val fakeDatabase = object : IInternalCounterUserActivityDatabase { - override suspend fun submitDirect(activity: DatabaseBackedCounterUserActivity, - diff: Int, - instant: Instant, - extra: Map?) { - endedEvents[activity.id] = diff - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - onDatabaseDeath.add(action) - } - } - - val submissionLock = Mutex(true) - val throttlerCoroutine = launch { - CounterUserActivityDatabaseThrottler(this, fakeDatabase, 10.minutes).apply { - submit(myActivity1, expected[myActivity1.id]!!) - submit(myActivity2, (expected[myActivity2.id]!!)/2) - submit(myActivity2, (expected[myActivity2.id]!!)/2) - submissionLock.unlock() - } - } - - submissionLock.lock() - - for (task in onDatabaseDeath) { - task(true) - } - - throttlerCoroutine.cancel() - } - - for (expectedValue in expected) { - Assert.assertTrue(endedEvents.contains(expectedValue.key)) - Assert.assertEquals(expectedValue.value, endedEvents[expectedValue.key]) - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/databaseTestRunners.kt b/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/databaseTestRunners.kt deleted file mode 100644 index 38b1516929b5..000000000000 --- a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/databaseTestRunners.kt +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.tests.dbs - -import com.intellij.ae.database.core.dbs.IUserActivityDatabaseLayer -import com.intellij.ae.database.core.dbs.SqliteLazyInitializedDatabase -import com.intellij.testFramework.common.timeoutRunBlocking -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -/** - * Runs a test for [IUserActivityDatabaseLayer] - */ -fun runDatabaseLayerTest( - dbFactory: (CoroutineScope) -> T, - action: suspend (T) -> Unit, -) = runInitializedDatabaseTestInternal { cs, db -> action(dbFactory(cs)) } - -fun runDatabaseLayerTest( - dbFactory: (CoroutineScope) -> T, - action: suspend (T, SqliteLazyInitializedDatabase) -> Unit, -) = runInitializedDatabaseTestInternal { cs, db -> action(dbFactory(cs), db) } - -fun runDatabaseLayerTest( - dbFactory: (CoroutineScope) -> T, - action: suspend (T, SqliteLazyInitializedDatabase, CoroutineScope) -> Unit, -) = runInitializedDatabaseTestInternal { cs, db -> action(dbFactory(cs), db, cs) } - -private fun runInitializedDatabaseTestInternal(action: suspend (CoroutineScope, SqliteLazyInitializedDatabase) -> Unit) { - timeoutRunBlocking { - withContext(Dispatchers.IO) { - val db = SqliteLazyInitializedDatabase.getInstanceAsync() - action(this, db) - db.closeDatabase() - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/timespan/TimeSpanUserActivityDatabaseTest.kt b/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/timespan/TimeSpanUserActivityDatabaseTest.kt deleted file mode 100644 index 267a453832b1..000000000000 --- a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/timespan/TimeSpanUserActivityDatabaseTest.kt +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.core.tests.dbs.timespan - -import com.intellij.ae.database.core.activities.DatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.activities.WritableDatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.dbs.SqliteLazyInitializedDatabase -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabase -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabaseManualKind -import com.intellij.ae.database.core.tests.dbs.runDatabaseLayerTest -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.testFramework.fixtures.BasePlatformTestCase -import kotlinx.coroutines.CoroutineScope -import org.jetbrains.sqlite.ObjectBinderFactory -import org.junit.jupiter.api.Assertions -import java.time.Instant -import kotlin.time.Duration.Companion.seconds -import kotlin.time.toJavaDuration - -class TimeSpanUserActivityDatabaseTest : BasePlatformTestCase() { - private val databaseFactory = { cs: CoroutineScope -> - TimeSpanUserActivityDatabase(cs).also { it -> it.cancelBackgroundUpdate() } - } - - private data class TestEvent( - val dbId: Int, - val activityId: String, - val startedAt: String, - val endAt: String, - val isFinished: Boolean, - ) - - private suspend fun SqliteLazyInitializedDatabase.getActivityEvents(activityId: String): List { - return execute { db -> - val stmt = db.prepareStatement( - """ - SELECT id, activity_id, started_at, ended_at, is_finished - FROM "timespanUserActivity" - WHERE activity_id = ? - """.trimIndent(), - ObjectBinderFactory.create1(), - ) - - val result = mutableListOf() - stmt.binder.bind(activityId) - stmt.executeQuery().let { - while (it.next()) { - result.add(TestEvent( - it.getInt(0), - it.getString(1)!!, - it.getString(2)!!, - it.getString(3)!!, - it.getBoolean(4), - )) - } - } - - result - } ?: error("Unexpected null in getActivityEvents") - } - - fun testEndAllEvents() = runDatabaseLayerTest(databaseFactory) { db, initDb, cs -> - val testActivity1 = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testEndAllActivity1" - } - - val testActivity2 = object : WritableDatabaseBackedTimeSpanUserActivity() { - override val canBeStale: Boolean = true - override val id: String get() = "testEndAllActivity2" - } - - db.submitManual( - testActivity1, - id=testActivity1.id, - canBeStale=false, - kind=TimeSpanUserActivityDatabaseManualKind.Start, - moment= Instant.now() - ) - db.submitPeriodicEvent(testActivity2, id=testActivity2.id) - Assertions.assertTrue(initDb.getActivityEvents(testActivity1.id).all { !it.isFinished }) - Assertions.assertTrue(initDb.getActivityEvents(testActivity2.id).all { !it.isFinished }) - db.endAllEvents() - Assertions.assertTrue(initDb.getActivityEvents(testActivity1.id).all { it.isFinished }) - Assertions.assertTrue(initDb.getActivityEvents(testActivity2.id).all { it.isFinished }) - } - - fun testRemoveEvent() = runDatabaseLayerTest(databaseFactory) { db, initDb, cs -> - val testActivity1 = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testRemoveActivity1" - } - - db.endEventInternal( - null, - testActivity1, - InstantUtils.Now, - InstantUtils.NowButABitLater, - false, - ) - - Assertions.assertEquals(1, initDb.getActivityEvents(testActivity1.id).size) - db.removeEvent(testActivity1) - Assertions.assertEquals(0, initDb.getActivityEvents(testActivity1.id).size) - } - - fun testEndEventInternal() = runDatabaseLayerTest(databaseFactory) { db, initDb, cs -> - val testActivity = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testEndActivity1" - } - - // Assert event stored correctly on first call - val initialStart = InstantUtils.Now - val initialEnd = InstantUtils.NowButABitLater - val insertedId = db.endEventInternal(null, testActivity, initialStart, initialEnd, isFinished = false) - - // Assert event stored correctly on first call - val storedEvents1 = initDb.getActivityEvents(testActivity.id) - Assertions.assertEquals(1, storedEvents1.size) - val storedEvent1 = storedEvents1.first() - Assertions.assertEquals(insertedId, storedEvent1.dbId) - Assertions.assertEquals(testActivity.id, storedEvent1.activityId) - Assertions.assertFalse(storedEvent1.isFinished) - Assertions.assertEquals(InstantUtils.formatForDatabase(initialStart), storedEvent1.startedAt) - Assertions.assertEquals(InstantUtils.formatForDatabase(initialEnd), storedEvent1.endAt) - - val updatedEnd = initialEnd + 10.seconds.toJavaDuration() - val updatedId = db.endEventInternal(insertedId, testActivity, initialEnd, updatedEnd, isFinished = true) - - // Assert id didn't change - Assertions.assertEquals(insertedId, updatedId) - - // Assert event updated correctly on consequent calls - val storedEvents2 = initDb.getActivityEvents(testActivity.id) - Assertions.assertEquals(1, storedEvents2.size) - val storedEvent2 = storedEvents2.first() - Assertions.assertEquals(updatedId, storedEvent2.dbId) - Assertions.assertEquals(testActivity.id, storedEvent2.activityId) - Assertions.assertTrue(storedEvent2.isFinished) - Assertions.assertEquals(InstantUtils.formatForDatabase(initialStart), storedEvent2.startedAt) - Assertions.assertEquals(InstantUtils.formatForDatabase(updatedEnd), storedEvent2.endAt) - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/timespan/TimeSpanUserActivityDatabaseThrottlerTest.kt b/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/timespan/TimeSpanUserActivityDatabaseThrottlerTest.kt deleted file mode 100644 index 96cce6adf6b3..000000000000 --- a/plugins/feature-usage-database/core/tests/com/intellij/ae/database/core/tests/dbs/timespan/TimeSpanUserActivityDatabaseThrottlerTest.kt +++ /dev/null @@ -1,417 +0,0 @@ -package com.intellij.ae.database.core.tests.dbs.timespan - -import com.intellij.ae.database.core.activities.DatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.dbs.timespan.IInternalTimeSpanUserActivityDatabase -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabaseManualKind -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabaseThrottler -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.testFramework.common.timeoutRunBlocking -import com.intellij.testFramework.fixtures.BasePlatformTestCase -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.junit.Assert -import java.time.Duration -import java.time.Instant -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds -import kotlin.time.toKotlinDuration - -class TimeSpanUserActivityDatabaseThrottlerTest : BasePlatformTestCase() { - private val updatePause = 4.seconds - private val eventTtl = Duration.ofSeconds(2L) - - private fun runCounterUserActivityTest( - submit: suspend (TimeSpanUserActivityDatabaseThrottler, DatabaseBackedTimeSpanUserActivity) -> Unit, - asserts: ( - activity: DatabaseBackedTimeSpanUserActivity, - startedAt: Instant, - endedAt: Instant, - isFinished: Boolean, - lock: Mutex - ) -> Unit) { - val myActivity = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testActivity" - } - - val lock = Mutex() - - val onDatabaseDeath = mutableListOf Unit>() - - val fakeDatabase = object : IInternalTimeSpanUserActivityDatabase { - override suspend fun endEventInternal(databaseId: Int?, - activity: DatabaseBackedTimeSpanUserActivity, - startedAt: Instant, - endedAt: Instant, - isFinished: Boolean, - extra: Map?): Int { - asserts(activity, startedAt, endedAt, isFinished, lock) - return 0 - } - - override suspend fun endAllEvents(): Int { - return 0 - } - - override suspend fun removeEvent(activity: DatabaseBackedTimeSpanUserActivity): Int { - error("Called removeEvent which doesn't work in current test") - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - error("Called executeBeforeConnectionClosed which doesnt work in current test") - } - } - - timeoutRunBlocking { - val throttlerCoroutine = launch { - TimeSpanUserActivityDatabaseThrottler(this, fakeDatabase, updatePause, eventTtl).apply { - submit(this, myActivity) - } - } - - val submissionTime = Instant.now() - - lock.withLock { - println("Took ${Duration.between(submissionTime, Instant.now()).seconds} seconds to submit event") - for (task in onDatabaseDeath) { - task() - } - throttlerCoroutine.cancel() - } - } - } - - fun testOnePeriodicNoUpdate() { - var submittedAt: Instant = InstantUtils.Now - - val submit: suspend (TimeSpanUserActivityDatabaseThrottler, DatabaseBackedTimeSpanUserActivity) -> Unit = { thr, activity -> - submittedAt = thr.submitPeriodic(activity, "testing", false, null)!! - } - - val verify: (DatabaseBackedTimeSpanUserActivity, Instant, Instant, Boolean, Mutex) -> Unit = { activity, start, end, isFinished, lock -> - Assert.assertEquals(submittedAt, start) - Assert.assertEquals(submittedAt + Duration.ofSeconds(eventTtl.seconds / 2), end) - Assert.assertTrue(isFinished) - lock.unlock() - } - - runCounterUserActivityTest(submit, verify) - } - - fun testOnePeriodicWithUpdate() { - var submittedAt = InstantUtils.Now - - val submit: suspend (TimeSpanUserActivityDatabaseThrottler, DatabaseBackedTimeSpanUserActivity) -> Unit = { thr, activity -> - submittedAt = thr.submitPeriodic(activity, "testing", false, null)!! - delay(1.seconds) - thr.submitPeriodic(activity, "testing", false, null) - } - - val verify: (DatabaseBackedTimeSpanUserActivity, Instant, Instant, Boolean, Mutex) -> Unit = { activity, start, end, isFinished, lock -> - Assert.assertEquals(submittedAt, start) - Assert.assertNotEquals(submittedAt + Duration.ofSeconds(eventTtl.seconds / 2), end) - Assert.assertTrue(isFinished) - lock.unlock() - } - - runCounterUserActivityTest(submit, verify) - } - - fun testResubmitPeriodicAsManualFails() { - val submit: suspend (TimeSpanUserActivityDatabaseThrottler, DatabaseBackedTimeSpanUserActivity) -> Unit = { thr, activity -> - val periodicSubmitTime = thr.submitPeriodic(activity, "testing", false, null) - Assert.assertNotNull(periodicSubmitTime) - - val manualSubmitTime = thr.submitManual( - activity, - "testing", - TimeSpanUserActivityDatabaseManualKind.Start, - canBeStale = false, - moment = InstantUtils.Now, - extra = null, - ) - Assert.assertNull(manualSubmitTime) - } - - val verify: (DatabaseBackedTimeSpanUserActivity, Instant, Instant, Boolean, Mutex) -> Unit = { _, _, _, _, _ -> } - - runCounterUserActivityTest(submit, verify) - } - - fun testResubmitManualAsPeriodicFails() { - val submit: suspend (TimeSpanUserActivityDatabaseThrottler, DatabaseBackedTimeSpanUserActivity) -> Unit = { thr, activity -> - val periodicSubmitTime = thr.submitPeriodic(activity, "testing", false, null) - Assert.assertNotNull(periodicSubmitTime) - - val manualSubmitTime = thr.submitManual( - activity, - "testing", - TimeSpanUserActivityDatabaseManualKind.Start, - canBeStale = false, - moment = InstantUtils.Now, - extra = null, - ) - Assert.assertNull(manualSubmitTime) - } - - val verify: (DatabaseBackedTimeSpanUserActivity, Instant, Instant, Boolean, Mutex) -> Unit = { _, _, _, _, _ -> } - runCounterUserActivityTest(submit, verify) - } - - fun testShowdown() { - val myActivity1 = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testActivity1" - } - val myActivity2 = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testActivity2" - } - - val onDatabaseDeath = mutableListOf Unit>() - - timeoutRunBlocking { - val endedEvents = mutableListOf() - - val fakeDatabase = object : IInternalTimeSpanUserActivityDatabase { - override suspend fun endAllEvents(): Int { - return 0 - } - - override suspend fun removeEvent(activity: DatabaseBackedTimeSpanUserActivity): Int { - return 1 - } - - override suspend fun endEventInternal(databaseId: Int?, - activity: DatabaseBackedTimeSpanUserActivity, - startedAt: Instant, - endedAt: Instant, - isFinished: Boolean, - extra: Map?): Int { - endedEvents.add(activity.id) - return 0 - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - onDatabaseDeath.add(action) - } - } - - val submissionLock = Mutex(true) - val throttlerCoroutine = launch { - TimeSpanUserActivityDatabaseThrottler(this, fakeDatabase, 10.minutes, Duration.ofNanos(1)).apply { - submitPeriodic(myActivity1, myActivity1.id, false, null) - submitManual(myActivity2, myActivity2.id, TimeSpanUserActivityDatabaseManualKind.Start, true, InstantUtils.Now, null) - submitManual(myActivity2, myActivity2.id, TimeSpanUserActivityDatabaseManualKind.End, true, InstantUtils.NowButABitLater, null) - submissionLock.unlock() - } - } - - submissionLock.lock() - - for (task in onDatabaseDeath) { - task(true) - } - - throttlerCoroutine.cancel() - - Assert.assertTrue(endedEvents.contains(myActivity1.id)) - Assert.assertTrue(endedEvents.contains(myActivity2.id)) - } - } - - fun testUnfinishedSaved() { - val myActivity1 = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testActivity1" - } - val myActivity2 = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testActivity2" - } - - val eventTtl = Duration.ofMillis(30) - val updateInterval = 10.milliseconds - - val onDatabaseDeath = mutableListOf Unit>() - - timeoutRunBlocking { - val endedEvents = mutableListOf() - val savedEvents = mutableMapOf() - - val fakeDatabase = object : IInternalTimeSpanUserActivityDatabase { - var idSeq = 0 - - private fun nextId(): Int { - idSeq += 1 - return idSeq - } - - override suspend fun endAllEvents(): Int { - return 0 - } - - override suspend fun removeEvent(activity: DatabaseBackedTimeSpanUserActivity): Int { - error("This method shouldn't be called in this test") - } - - override suspend fun endEventInternal(databaseId: Int?, - activity: DatabaseBackedTimeSpanUserActivity, - startedAt: Instant, - endedAt: Instant, - isFinished: Boolean, - extra: Map?): Int { - val itemId = databaseId ?: nextId() - if (isFinished) { - endedEvents.add(activity.id) - } else { - savedEvents[activity.id] = itemId - } - return itemId - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - onDatabaseDeath.add(action) - } - } - - val firstSubmissionLock = Mutex(true) - val secondSubmissionLock = Mutex(true) - val assertionLock = Mutex(true) - - val throttlerCoroutine = launch { - TimeSpanUserActivityDatabaseThrottler( - this, - fakeDatabase, - updateInterval, - eventTtl, - ).apply { - submitPeriodic(myActivity1, myActivity1.id, false, null) - submitManual(myActivity2, myActivity2.id, TimeSpanUserActivityDatabaseManualKind.Start, true, InstantUtils.Now, null) - firstSubmissionLock.unlock() - assertionLock.lock() - submitManual(myActivity2, myActivity2.id, TimeSpanUserActivityDatabaseManualKind.End, true, InstantUtils.Now, null) - secondSubmissionLock.unlock() - } - } - - firstSubmissionLock.lock() - - // Wait to commit changes once - delay(updateInterval.times(1.1)) - Assert.assertTrue(savedEvents.contains(myActivity1.id)) - Assert.assertTrue(savedEvents.contains(myActivity2.id)) - Assert.assertTrue(endedEvents.isEmpty()) - - val activity1DbId = savedEvents[myActivity1.id] - val activity2DbId = savedEvents[myActivity2.id] - - assertionLock.unlock() - - // Wait for second submission - secondSubmissionLock.lock() - - // Wait for event ttl to expire - delay(eventTtl.toKotlinDuration()) - - // Assert both events ended - Assert.assertTrue(endedEvents.contains(myActivity1.id)) - Assert.assertTrue(endedEvents.contains(myActivity2.id)) - - // Assert id wasn't changed (called with the same dbId) - Assert.assertEquals(activity1DbId, savedEvents[myActivity1.id]) - Assert.assertEquals(activity2DbId, savedEvents[myActivity2.id]) - - throttlerCoroutine.cancel() - } - } - - fun testDanglingEventsCommited() { - val onDatabaseDeath = mutableListOf Unit>() - - timeoutRunBlocking { - var endAllEventsCalled = false - - val fakeDatabase = object : IInternalTimeSpanUserActivityDatabase { - override suspend fun endAllEvents(): Int { - endAllEventsCalled = true - return 1 - } - - override suspend fun removeEvent(activity: DatabaseBackedTimeSpanUserActivity): Int { - error("This method shouldn't be called in this test") - } - - override suspend fun endEventInternal(databaseId: Int?, - activity: DatabaseBackedTimeSpanUserActivity, - startedAt: Instant, - endedAt: Instant, - isFinished: Boolean, - extra: Map?): Int { - error("This method shouldn't be called in this test") - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - onDatabaseDeath.add(action) - } - } - - val initLock = Mutex(true) - - val throttlerCoroutine = launch { - TimeSpanUserActivityDatabaseThrottler(this, fakeDatabase) - initLock.unlock() - } - - initLock.withLock { - Assert.assertTrue(endAllEventsCalled) - throttlerCoroutine.cancel() - } - } - } - - fun testCancelActivity() { - val activity = object : DatabaseBackedTimeSpanUserActivity() { - override val id: String get() = "testActivity" - } - - timeoutRunBlocking { - val removedEvents = mutableListOf() - - val fakeDatabase = object : IInternalTimeSpanUserActivityDatabase { - override suspend fun endAllEvents(): Int { - return 1 - } - - override suspend fun removeEvent(activity: DatabaseBackedTimeSpanUserActivity): Int { - removedEvents.add(activity.id) - return 1 - } - - override suspend fun endEventInternal(databaseId: Int?, - activity: DatabaseBackedTimeSpanUserActivity, - startedAt: Instant, - endedAt: Instant, - isFinished: Boolean, - extra: Map?): Int { - error("This method shouldn't be called in this test") - } - - override fun executeBeforeConnectionClosed(action: suspend (isFinal: Boolean) -> Unit) { - } - } - - val cancelLock = Mutex(true) - - val throttlerCoroutine = launch { - val throttler = TimeSpanUserActivityDatabaseThrottler(this, fakeDatabase) - throttler.submitPeriodic(activity, activity.id, false, null) - throttler.cancel(activity, activity.id) - cancelLock.unlock() - } - - Assert.assertFalse(removedEvents.contains(activity.id)) - cancelLock.lock() - Assert.assertTrue(removedEvents.contains(activity.id)) - throttlerCoroutine.cancel() - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/BUILD.bazel b/plugins/feature-usage-database/counters/BUILD.bazel deleted file mode 100644 index 940fbf2dff48..000000000000 --- a/plugins/feature-usage-database/counters/BUILD.bazel +++ /dev/null @@ -1,26 +0,0 @@ -### auto-generated section `build intellij.ae.database.counters.community` start -load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_resources") - -jvm_resources( - name = "ae-database-counters-community_resources", - files = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -jvm_library( - name = "ae-database-counters-community", - module_name = "intellij.ae.database.counters.community", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java"], allow_empty = True), - deps = [ - "@lib//:kotlin-stdlib", - "//platform/platform-impl:ide-impl", - "//platform/xdebugger-api:debugger", - "//platform/lang-impl", - "//platform/vcs-api:vcs", - "//plugins/feature-usage-database/core:ae-database-core", - "//platform/sqlite", - ], - runtime_deps = [":ae-database-counters-community_resources"] -) -### auto-generated section `build intellij.ae.database.counters.community` end \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/intellij.ae.database.counters.community.iml b/plugins/feature-usage-database/counters/intellij.ae.database.counters.community.iml deleted file mode 100644 index 214207cb91bc..000000000000 --- a/plugins/feature-usage-database/counters/intellij.ae.database.counters.community.iml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/resources/intellij.ae.database.counters.community.xml b/plugins/feature-usage-database/counters/resources/intellij.ae.database.counters.community.xml deleted file mode 100644 index 62eb723650b6..000000000000 --- a/plugins/feature-usage-database/counters/resources/intellij.ae.database.counters.community.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - com.intellij.cwm.guest - com.intellij.jetbrains.client - com.intellij.gateway - - - - - \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/AltEnterActionInvocation.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/AltEnterActionInvocation.kt deleted file mode 100644 index c8eff6bf88ef..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/AltEnterActionInvocation.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.baseEvents.FusBasedCounterUserActivity -import com.intellij.ae.database.core.baseEvents.fus.FusEventCatcher -import com.intellij.ae.database.core.baseEvents.fus.FusEventDefinitionBuilder - -// todo record not popup opening, but intention call -object AltEnterActionInvocation : FusBasedCounterUserActivity() { - internal class Factory : FusEventCatcher.Factory { - override fun getInstance(): FusEventCatcher = catcher - } - - override fun define(): FusEventDefinitionBuilder { - return definition("alt.enter.invocation") { - event("actions", "action.finished") { - field("action_id", "ShowIntentionActions") - } - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/CodingTimeUserActivity.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/CodingTimeUserActivity.kt deleted file mode 100644 index 7ddb4cbd951c..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/CodingTimeUserActivity.kt +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.activities.WritableDatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.createMap -import com.intellij.ae.database.core.runUpdateEvent -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.lang.Language -import com.intellij.openapi.application.readAction -import com.intellij.openapi.command.CommandEvent -import com.intellij.openapi.command.CommandListener -import com.intellij.openapi.components.Service -import com.intellij.openapi.editor.Document -import com.intellij.openapi.editor.event.DocumentEvent -import com.intellij.openapi.editor.event.DocumentListener -import com.intellij.openapi.editor.event.EditorFactoryEvent -import com.intellij.openapi.editor.event.EditorFactoryListener -import com.intellij.openapi.editor.ex.EditorEx -import com.intellij.openapi.editor.ex.util.EditorUtil -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.validOrNull -import com.intellij.psi.impl.PsiManagerEx -import com.intellij.psi.util.validOrNull -import com.intellij.util.io.DigestUtil -import org.jetbrains.sqlite.ObjectBinderFactory -import java.math.BigInteger -import java.time.Instant -import java.util.* - -/** - * Coding activity. Stores language of the file and file hash - */ -object CodingTimeUserActivity : WritableDatabaseBackedTimeSpanUserActivity() { - override val canBeStale: Boolean - get() = true - override val id: String - get() = "editor.changing" - - suspend fun write(editorId: String, language: Language, file: VirtualFile) { - val extra = mapOf("lang" to language.id, "fileHash" to md5(file.presentableUrl)) - submitPeriodic(editorId, extra) - } - - /** - * How many files were edited in the period of [from]..[until] - */ - suspend fun getFilesEdited(from: Instant, until: Instant): Int? { - return getDatabase().execute { database -> - val filesEditedStatement = database - .prepareStatement("SELECT COUNT(DISTINCT json_extract(extra, '\$.fileHash')) FROM timespanUserActivity\n" + - "WHERE activity_id = '$id'\n" + - "AND datetime(started_at) >= datetime(?)\n" + - "AND datetime(ended_at) <= datetime(?)", ObjectBinderFactory.create2()) - - filesEditedStatement.binder.bind(InstantUtils.formatForDatabase(from), InstantUtils.formatForDatabase(until)) - filesEditedStatement.selectInt() ?: 0 - } - } - - /** - * How much files per language were edited / map of (language name -> length in seconds) - */ - suspend fun getByLanguageStat(from: Instant, until: Instant): Map? { - return getDatabase().execute { database -> - val byLanguageStatStatement = database - .prepareStatement("SELECT json_extract(extra, '\$.lang') as lang, SUM(strftime('%s', ended_at) - strftime('%s', started_at)) FROM timespanUserActivity\n" + - "WHERE activity_id = '$id'\n" + - "AND datetime(started_at) >= datetime(?)\n" + - "AND datetime(ended_at) <= datetime(?)\n" + - "GROUP BY lang", ObjectBinderFactory.create2()) - byLanguageStatStatement.binder.bind(InstantUtils.formatForDatabase(from), InstantUtils.formatForDatabase(until)) - val res = byLanguageStatStatement.executeQuery() - - createMap( - { it.getString(0) }, { it.getInt(1) }, - { k0, v0 -> k0 != null && v0 != 0 }, - { LanguageWrapper(Language.findLanguageByID(it!!), it) }, { it }, - { _, _ -> true }, - res - ) - } - } - - private fun md5(buffer: String): String { - val md5 = DigestUtil.md5() - md5.update(buffer.toByteArray(Charsets.UTF_8)) - return BigInteger(md5.digest()).abs().toString(16) - } -} - -/** - * Keeps language ID in case Language is not found - */ -data class LanguageWrapper( - val language: Language?, - val id: String -) - -@Service(Service.Level.PROJECT) -private class DocumentHolder { - companion object { - fun getInstance(project: Project) = project.getService(DocumentHolder::class.java) - } - - private val urls = mutableSetOf() - - fun submit(vf: VirtualFile) { - urls.add(vf.url) - } - - fun remove(vf: VirtualFile) { - urls.remove(vf.url) - } - - fun isAllowed(vf: VirtualFile): Boolean { - return urls.contains(vf.url) - } -} - -internal class CodingTimeUserActivityEditorFactoryListener : EditorFactoryListener { - override fun editorCreated(event: EditorFactoryEvent) { - val editor = event.editor as EditorEx - val project = editor.project ?: return - val editorId = UUID.randomUUID().toString() - - val disposable = Disposer.newDisposable() - EditorUtil.disposeWithEditor(editor, disposable) - - editor.document.addDocumentListener(object : DocumentListener { - override fun documentChanged(event: DocumentEvent) { - val vf = editor.virtualFile?.validOrNull() ?: return - if (DocumentHolder.getInstance(project).isAllowed(vf)) { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(CodingTimeUserActivity) { - val psiFile = readAction { - if (vf.isValid) { - PsiManagerEx.getInstance(project).findFile(vf)?.validOrNull() - } - else { - null - } - } ?: return@runUpdateEvent - it.write(editorId, psiFile.language, vf) - } - } - } - }, disposable) - } -} - -internal class CodingTimeUserActivityCommandListener : CommandListener { - override fun commandStarted(event: CommandEvent) { - val doc = event.commandGroupId as? Document - if (doc != null) { - val vf = FileDocumentManager.getInstance().getFile(doc)?.validOrNull() - val project = event.project - if (vf != null && project != null) { - DocumentHolder.getInstance(project).submit(vf) - } - } - } - - override fun commandFinished(event: CommandEvent) { - val doc = event.commandGroupId as? Document - if (doc != null) { - val vf = FileDocumentManager.getInstance().getFile(doc)?.validOrNull() - val project = event.project - if (vf != null && project != null) { - DocumentHolder.getInstance(project).remove(vf) - } - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/CompletionCharactersSpared.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/CompletionCharactersSpared.kt deleted file mode 100644 index 506dcc7a3aeb..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/CompletionCharactersSpared.kt +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.activities.ReadableUserActivity -import com.intellij.ae.database.core.activities.WritableDatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.runUpdateEvent -import com.intellij.featureStatistics.FeatureStatisticsUpdateListener - -/** - * Stat for 'Code completion has saved you from typing at least N characters' - */ -object CompletionCharactersSpared : ReadableUserActivity, WritableDatabaseBackedCounterUserActivity() { - override val id = "completion.spared" - - override suspend fun getActivityValue(): Int { - return getDatabase().getActivitySum(this, null, null) - } - - internal suspend fun write(newSpared: Int) { - submit(newSpared) - } -} - -internal class CompletionCharactersSparedListener : FeatureStatisticsUpdateListener { - override fun completionStatUpdated(spared: Int) { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(CompletionCharactersSpared) { - it.write(spared) - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/FeatureUsageDatabaseCountersScopeProvider.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/FeatureUsageDatabaseCountersScopeProvider.kt deleted file mode 100644 index b44513cede9a..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/FeatureUsageDatabaseCountersScopeProvider.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.intellij.ae.database.counters.community - -import com.intellij.openapi.Disposable -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import kotlinx.coroutines.CoroutineScope - -@Service -class FeatureUsageDatabaseCountersScopeProvider(private val cs: CoroutineScope) : Disposable { - companion object { - fun getDisposable(): Disposable = service() - fun getScope() = service().cs - - fun getDisposable(project: Project): Disposable = project.service() - fun getScope(project: Project) = project.service().cs - } - - override fun dispose() {} -} - -@Service(Service.Level.PROJECT) -private class FeatureUsageDatabaseCountersProjectScopeProvider(val cs: CoroutineScope) : Disposable { - override fun dispose() {} -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/IdeRunningUserActivity.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/IdeRunningUserActivity.kt deleted file mode 100644 index 3fdaa7af4533..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/IdeRunningUserActivity.kt +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.activities.WritableDatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabaseManualKind -import com.intellij.ae.database.core.runUpdateEvent -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.ide.AppLifecycleListener -import kotlinx.coroutines.delay - -object IdeRunningUserActivity : WritableDatabaseBackedTimeSpanUserActivity() { - override val canBeStale = true - override val id = "ide.running" - - private const val eventId = "ideRun" - - internal suspend fun writeStart() { - val moment = InstantUtils.Now - - // submit event later, because submitting it now would init all the db stuff - delay(5000) - submitManual(eventId, TimeSpanUserActivityDatabaseManualKind.Start, null, moment) - } -} - -internal class IdeStartedUserActivityListener : AppLifecycleListener { - /** - * Enough to write down when app was started, because event marked as canBeStale - */ - override fun appStarted() { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(IdeRunningUserActivity) { - it.writeStart() - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/PluginsInstalledCounterUserActivity.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/PluginsInstalledCounterUserActivity.kt deleted file mode 100644 index aa6f43a9141a..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/PluginsInstalledCounterUserActivity.kt +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.activities.WritableDatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.baseEvents.fus.FusEventCatcher -import com.intellij.ae.database.core.runUpdateEvent -import com.intellij.ide.plugins.DynamicPluginListener -import com.intellij.ide.plugins.IdeaPluginDescriptor -import com.intellij.ide.plugins.InstalledPluginsState -import com.intellij.openapi.components.* -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.extensions.PluginId -import java.time.Instant -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicLong - -/** - * Plugin is considered to be installed if it wasn't uninstalled in 10 minutes after installation. - * Plugin updates are ignored - */ -object PluginsInstalledCounterUserActivity : WritableDatabaseBackedCounterUserActivity() { - override val id: String - get() = "plugin.installed" - - - internal suspend fun writeInstallation(pluginId: String, time: Instant) { - val installedPlugins = InstalledPluginsPersistentState.getInstanceAsync() - if (!InstalledPluginsState.getInstance().wasUpdated(PluginId.getId(pluginId)) && installedPlugins.add(pluginId)) { - submit(1, time) - } - else { - installedPlugins.removeUpdated(pluginId) - } - } - - internal suspend fun writeUninstallation(pluginId: String) { - val installedPlugins = InstalledPluginsPersistentState.getInstanceAsync() - if (installedPlugins.remove(pluginId)) { - submit(-1) - } - } -} - -@Service -@State(name = "aeInstalledPlugins", storages = [(Storage("aeInstalledPlugins.xml", roamingType = RoamingType.DISABLED))]) -class InstalledPluginsPersistentState : PersistentStateComponentWithModificationTracker { - companion object { - fun getInstance() = service() - suspend fun getInstanceAsync() = serviceAsync() - } - data class State(var myPlugins: Map = HashMap()) - - private val PLUGIN_INSTALL_TTL_MS = 10 * 60 * 1000 - - private val installedPlugins = ConcurrentHashMap() - private val updatedPlugins = ConcurrentHashMap.newKeySet() - private val modTracker = AtomicLong(0) - - override fun getState() = State(installedPlugins.filterValues { System.currentTimeMillis() - it < PLUGIN_INSTALL_TTL_MS }) - - override fun getStateModificationCount() = modTracker.get() - - override fun loadState(state: State) { - for ((k, v) in state.myPlugins) { - installedPlugins[k] = v - } - } - - fun add(s: String): Boolean { - val installedPluginContains = installedPlugins[s]?.let { System.currentTimeMillis() - it < PLUGIN_INSTALL_TTL_MS } == true - if (installedPluginContains || updatedPlugins.contains(s)) { - return false - } - modTracker.incrementAndGet() - installedPlugins[s] = System.currentTimeMillis() - - return true - } - - fun addUpdated(s: String) { - updatedPlugins.add(s) - } - - fun removeUpdated(s: String) { - updatedPlugins.remove(s) - } - - // plugin will be recorded as removed if it was removed within [HALF_HOUR_MS] milliseconds - fun remove(s: String): Boolean { - modTracker.incrementAndGet() - val currTime = System.currentTimeMillis() - updatedPlugins.remove(s) - - return installedPlugins.remove(s)?.let { currTime - it < PLUGIN_INSTALL_TTL_MS } == true - } -} - -internal class MyDynamicPluginListener : DynamicPluginListener { - // we don't want to treat theme updates as new installations, workaround for IDEA-342821 - override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) { - if (isUpdate) { - InstalledPluginsPersistentState.getInstance().addUpdated(pluginDescriptor.pluginId.idString) - } - } -} - -internal class PluginInstalledFusListener : FusEventCatcher(), FusEventCatcher.Factory { - override fun getInstance() = this - - override fun define() = definition("plugin.installed") { - event("plugin.manager", "plugin.installation.finished") - } - - override suspend fun onEvent(fields: Map, eventTime: Instant) { - val pluginId = fields["plugin"] as? String ?: return - logger.info("Plugin installed: ${pluginId}.") - - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(PluginsInstalledCounterUserActivity) { - it.writeInstallation(pluginId, eventTime) - } - } -} - -internal class PluginUninstalledFusListener : FusEventCatcher(), FusEventCatcher.Factory { - override fun getInstance() = this - - override fun define() = definition("plugin.uninstalled") { - event("plugin.manager", "plugin.was.removed") - } - - override suspend fun onEvent(fields: Map, eventTime: Instant) { - val pluginId = fields["plugin"] as? String ?: return - logger.info("Plugin uninstalled: ${pluginId}.") - - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(PluginsInstalledCounterUserActivity) { - it.writeUninstallation(pluginId) - } - } -} - -private val logger = logger() \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/ProjectOpenedTimeSpanUserActivity.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/ProjectOpenedTimeSpanUserActivity.kt deleted file mode 100644 index 66278d0e2b7a..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/ProjectOpenedTimeSpanUserActivity.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.activities.WritableDatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabaseManualKind -import com.intellij.ae.database.core.runUpdateEvent -import com.intellij.openapi.project.Project -import com.intellij.openapi.project.ProjectCloseListener -import com.intellij.openapi.startup.ProjectActivity - -object ProjectOpenedTimeSpanUserActivity : WritableDatabaseBackedTimeSpanUserActivity() { - override val canBeStale: Boolean - get() = true - override val id: String - get() = "project.opened" - - suspend fun projectOpened(id: String, name: String) { - submitManual(id, TimeSpanUserActivityDatabaseManualKind.Start, mapOf("name" to name)) - } - - suspend fun projectClosed(id: String, name: String) { - submitManual(id, TimeSpanUserActivityDatabaseManualKind.End, mapOf("name" to name)) - } -} - -internal class ProjectOpenedTimeSpanUserActivityProjectOpenListener : ProjectActivity { - override suspend fun execute(project: Project) { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(ProjectOpenedTimeSpanUserActivity) { - it.projectOpened(project.locationHash, project.name) - } - } -} - -internal class ProjectOpenedTimeSpanUserActivityProjectCloseListener : ProjectCloseListener { - override fun projectClosing(project: Project) { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(ProjectOpenedTimeSpanUserActivity) { - it.projectClosed(project.locationHash, project.name) - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/RunConfigurationTimeSpanUserActivity.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/RunConfigurationTimeSpanUserActivity.kt deleted file mode 100644 index 9b43dea60793..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/RunConfigurationTimeSpanUserActivity.kt +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.activities.WritableDatabaseBackedTimeSpanUserActivity -import com.intellij.ae.database.core.dbs.timespan.TimeSpanUserActivityDatabaseManualKind -import com.intellij.ae.database.core.runUpdateEvent -import com.intellij.ae.database.core.utils.InstantUtils -import com.intellij.build.BuildProgressListener -import com.intellij.build.BuildViewManager -import com.intellij.build.events.BuildEvent -import com.intellij.build.events.FinishBuildEvent -import com.intellij.build.events.StartBuildEvent -import com.intellij.execution.ExecutionListener -import com.intellij.execution.executors.DefaultDebugExecutor -import com.intellij.execution.process.ProcessHandler -import com.intellij.execution.runners.ExecutionEnvironment -import com.intellij.ide.plugins.cl.PluginAwareClassLoader -import com.intellij.openapi.components.serviceAsync -import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.ProjectActivity -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.jetbrains.sqlite.ObjectBinderFactory -import java.time.Instant - -// todo check all run configurations (run with profile, run coverage) - -/** - * Writes events related to run configurations. - */ -object RunConfigurationTimeSpanUserActivity : WritableDatabaseBackedTimeSpanUserActivity() { - override val canBeStale = true - // TODO add flag 'errorProne' that fill turn off logs about bad end? - override val id = "runconfig.running" - - private val lastBuildLock = Mutex() - private var lastBuildAt = 0L - private var lastAttemptFromBuildListener: Boolean? = null - - suspend fun writeBuildStart(id: Int, timeStart: Long, attemptFromBuildListener: Boolean) { - /** - * Sometimes [BuildProgressListener] reports events, sometimes [ExecutionListener]. - * Sometimes they both report build events. So if a build event happened within 300ms and other event is from different place, we will skip it - */ - lastBuildLock.withLock { - val diff = timeStart - lastBuildAt - - if (diff <= 300 && lastAttemptFromBuildListener != attemptFromBuildListener) { - return - } - else { - lastBuildAt = timeStart - lastAttemptFromBuildListener = attemptFromBuildListener - } - } - - val data = mapOf("act" to RunConfigurationEventKind.Build.eventName) - submitManual(id.toString(), TimeSpanUserActivityDatabaseManualKind.Start, data) - } - - suspend fun writeRunConfigurationStart(kind: RunConfigurationEventKind, id: Int, timeStart: Long) { - val data = mapOf("act" to kind.eventName) - submitManual(id.toString(), TimeSpanUserActivityDatabaseManualKind.Start, data) - } - - suspend fun writeEnd(id: Int) { - submitManual(id.toString(), TimeSpanUserActivityDatabaseManualKind.End, null) - } - - /** - * @return sum of sessions lengths of specific [kind] in the period [from]..[until] in seconds - */ - suspend fun getSessionsLength(kind: RunConfigurationEventKind, from: Instant, until: Instant): Int? { - return getDatabase().execute { database -> - val sessionsLengthStatement = database - .prepareStatement("SELECT SUM(strftime('%s', ended_at) - strftime('%s', started_at)) FROM timespanUserActivity\n" + - "WHERE activity_id = '$id' " + - "AND json_extract(extra, '\$.act') = ? " + - "AND datetime(started_at) >= datetime(?) " + - "AND datetime(ended_at) <= datetime(?)", ObjectBinderFactory.create3()) - sessionsLengthStatement.binder.bind(kind.eventName, InstantUtils.formatForDatabase(from), InstantUtils.formatForDatabase(until)) - sessionsLengthStatement.selectInt() ?: 0 - } - } - - /** - * @return sum of sessions lengths grouped by kind in the period [from]..[until] in seconds - */ - suspend fun getAllSessionsLength(from: Instant, until: Instant): Map? { - return getDatabase().execute { database -> - val allSessionsLengthStatement = database - .prepareStatement("SELECT json_extract(extra, '\$.act') as field, SUM(strftime('%s', ended_at) - strftime('%s', started_at)) FROM timespanUserActivity\n" + - "WHERE activity_id = '$id' " + - "AND datetime(started_at) >= datetime(?) " + - "AND datetime(ended_at) <= datetime(?) " + - "GROUP BY field", ObjectBinderFactory.create2()) - allSessionsLengthStatement.binder.bind(InstantUtils.formatForDatabase(from), InstantUtils.formatForDatabase(until)) - val res = allSessionsLengthStatement.executeQuery() - val map = mutableMapOf() - while (res.next()) { - val keyString = res.getString(0) - val valueInt = res.getInt(1) - if (keyString == null || valueInt == 0) continue - - val k = RunConfigurationEventKind.fromString(keyString) ?: continue - - map[k] = valueInt - } - - map - } - } -} - -enum class RunConfigurationEventKind(val eventName: String) { - Run("run"), Debug("debug"), Build("build"); - - companion object { - fun fromString(eventName: String) = entries.find { it.eventName == eventName } - } -} - -internal class RunConfigurationListener : ExecutionListener { - override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) { - if (!isAllowed(env)) return - val id = System.identityHashCode(handler) // not the best ID out there, but it works - - val timeStart = System.currentTimeMillis() - - when { // should be similar to [isAllowed] - env.runProfile.name.let { it.contains("[build", true) || it.startsWith("build", true) } -> { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(RunConfigurationTimeSpanUserActivity) { - it.writeBuildStart(id, timeStart, false) - } - } - env.executor is DefaultDebugExecutor || env.executor.id.contains("debug", true) -> { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(RunConfigurationTimeSpanUserActivity) { - it.writeRunConfigurationStart(RunConfigurationEventKind.Debug, id, timeStart) - } - } - else -> { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(RunConfigurationTimeSpanUserActivity) { - it.writeRunConfigurationStart(RunConfigurationEventKind.Run, id, timeStart) - } - } - } - } - - override fun processTerminated(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler, exitCode: Int) { - if (!isAllowed(env)) return - - val id = System.identityHashCode(handler) - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(RunConfigurationTimeSpanUserActivity) { - it.writeEnd(id) - } - } - - private fun isAllowed(env: ExecutionEnvironment): Boolean { - // todo: extension point - if ((env.runProfile.javaClass.classLoader as? PluginAwareClassLoader)?.pluginId?.idString == "com.intellij.database") { - return false - } - - return true - } -} - -internal class BuildListenerProjectActivity : ProjectActivity { - override suspend fun execute(project: Project) { - project.serviceAsync().addListener( - BuildListener(), - FeatureUsageDatabaseCountersScopeProvider.getDisposable(project) - ) - } -} - -internal class BuildListener : BuildProgressListener { - override fun onEvent(buildId: Any, event: BuildEvent) { - val id = System.identityHashCode(buildId) - val timeStart = System.currentTimeMillis() - when (event) { - is StartBuildEvent -> { - // Skip Gradle initial loading - if (event.buildDescriptor.title == "Classes up-to-date check") return - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(RunConfigurationTimeSpanUserActivity) { - it.writeBuildStart(id, timeStart, true) - } - } - is FinishBuildEvent -> { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(RunConfigurationTimeSpanUserActivity) { - it.writeEnd(id) - } - } - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/TestFileCreatedUserActivity.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/TestFileCreatedUserActivity.kt deleted file mode 100644 index 42dc046c33d7..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/TestFileCreatedUserActivity.kt +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.activities.WritableDatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.runUpdateEvent -import com.intellij.openapi.application.readAction -import com.intellij.openapi.fileTypes.PlainTextLanguage -import com.intellij.openapi.project.ProjectLocator -import com.intellij.openapi.roots.TestSourcesFilter -import com.intellij.openapi.vfs.AsyncFileListener -import com.intellij.openapi.vfs.AsyncFileListener.ChangeApplier -import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent -import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent -import com.intellij.openapi.vfs.newvfs.events.VFileEvent -import com.intellij.psi.impl.PsiManagerEx -import com.intellij.psi.util.validOrNull - -object TestFileCreatedUserActivity : WritableDatabaseBackedCounterUserActivity() { - override val id = "test.file.created" - - suspend fun write() { - submit(1) - } -} - -internal class TestFileCreationListener : AsyncFileListener { - override fun prepareChange(events: MutableList): ChangeApplier { - val filteredEvents = events.filter { it is VFileCreateEvent || it is VFileCopyEvent } - - return object : ChangeApplier { - override fun afterVfsChange() { - for (event in filteredEvents) { - val file = event.file ?: return - val project = ProjectLocator.getInstance().guessProjectForFile(file) ?: return - val isTest = TestSourcesFilter.isTestSources(file, project) - if (isTest) { - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(TestFileCreatedUserActivity) { - val psiFile = readAction { - PsiManagerEx.getInstance(project).findFile(file)?.validOrNull() - } - - if (psiFile == null) { - return@runUpdateEvent - } - - // try to write only actual tests, not test data and etc - if (psiFile.language != PlainTextLanguage.INSTANCE && !file.fileType.isBinary) { - it.write() - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/VcsFileCommittedUserActivity.kt b/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/VcsFileCommittedUserActivity.kt deleted file mode 100644 index a68f2ebbcccc..000000000000 --- a/plugins/feature-usage-database/counters/src/com/intellij/ae/database/counters/community/VcsFileCommittedUserActivity.kt +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ae.database.counters.community - -import com.intellij.ae.database.core.activities.WritableDatabaseBackedCounterUserActivity -import com.intellij.ae.database.core.runUpdateEvent -import com.intellij.openapi.vcs.CheckinProjectPanel -import com.intellij.openapi.vcs.changes.CommitContext -import com.intellij.openapi.vcs.checkin.CheckinHandler -import com.intellij.openapi.vcs.checkin.CheckinHandlerFactory -import java.time.Instant - -object VcsFileCommittedUserActivity : WritableDatabaseBackedCounterUserActivity() { - override val id = "vcs.changes" - - suspend fun getFilesCommitted(from: Instant, until: Instant): Int { - return getDatabase().getActivitySum(this, from, until) - } - - suspend fun write(filesChanged: Int) { - submit(filesChanged) - } -} - -internal class VcsListenerFactory : CheckinHandlerFactory() { - override fun createHandler(panel: CheckinProjectPanel, commitContext: CommitContext): CheckinHandler { - return object : CheckinHandler() { - override fun checkinSuccessful() { - val count = panel.files.size - FeatureUsageDatabaseCountersScopeProvider.getScope().runUpdateEvent(VcsFileCommittedUserActivity) { - it.write(count) - } - } - } - } -} \ No newline at end of file diff --git a/plugins/feature-usage-database/plugin-community/BUILD.bazel b/plugins/feature-usage-database/plugin-community/BUILD.bazel deleted file mode 100644 index 355fbd5b05af..000000000000 --- a/plugins/feature-usage-database/plugin-community/BUILD.bazel +++ /dev/null @@ -1,20 +0,0 @@ -### auto-generated section `build intellij.ae.database.community` start -load("@rules_java//java:defs.bzl", "java_library") -load("@rules_jvm//:jvm.bzl", "jvm_resources") - -jvm_resources( - name = "ae-database-community_resources", - files = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -java_library( - name = "ae-database-community", - visibility = ["//visibility:public"], - runtime_deps = [ - ":ae-database-community_resources", - "//plugins/feature-usage-database/core:ae-database-core", - "//plugins/feature-usage-database/counters:ae-database-counters-community", - ] -) -### auto-generated section `build intellij.ae.database.community` end \ No newline at end of file diff --git a/plugins/feature-usage-database/plugin-community/intellij.ae.database.community.iml b/plugins/feature-usage-database/plugin-community/intellij.ae.database.community.iml deleted file mode 100644 index ae789f868f59..000000000000 --- a/plugins/feature-usage-database/plugin-community/intellij.ae.database.community.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/plugins/feature-usage-database/plugin-community/resources/META-INF/plugin.xml b/plugins/feature-usage-database/plugin-community/resources/META-INF/plugin.xml deleted file mode 100644 index 2f9f8808612a..000000000000 --- a/plugins/feature-usage-database/plugin-community/resources/META-INF/plugin.xml +++ /dev/null @@ -1,26 +0,0 @@ - - com.jetbrains.ae.database - Local Productivity Statistics - The Local Productivity Statistics plugin gathers local statistics on your machine about how the IDE is used. - You will be provided with an overview of your usage at the end of the year.

-

For the purpose of Local Productivity Statistics plugin, no data are transferred from your computer, - and the database is not shared or synchronized with any remote storage or third-party service. - JetBrains does not have access to your personal data created in the IDEs. - For more information on data protection, please click here: - https://www.jetbrains.com/legal/docs/privacy/privacy/

- ]]>
- JetBrains - - com.intellij.cwm.guest - com.intellij.jetbrains.client - com.intellij.gateway - com.intellij.modules.ultimate - - - - - - - com.intellij.modules.platform -
\ No newline at end of file