From 29bdecfcdd81f586854cd4b1cc6d138499cc22aa Mon Sep 17 00:00:00 2001 From: "Svetlana.Zemlyanskaya" Date: Fri, 7 Jun 2019 20:56:15 +0200 Subject: [PATCH] IDEA-CR-48136: FUS: record numerical fields if settings reporting is enabled (FUS-444) (cherry picked from commit bdf5420031054affd584a7b9099e4a2fc496623f) GitOrigin-RevId: e5c4a5b6fc50bf3bf39f7f4cfc7687888af99cb4 --- .../eventLog/FeatureUsageSettingsEvents.kt | 54 +++++-- .../FeatureUsageSettingsEventsTest.kt | 153 ++++++++++++++++-- .../com/intellij/ide/ui/UISettingsState.kt | 4 + .../fus/FeatureUsageEventLoggerProvider.kt | 2 +- .../openapi/components/ReportValue.java | 19 +++ 5 files changed, 199 insertions(+), 33 deletions(-) create mode 100644 platform/projectModel-api/src/com/intellij/openapi/components/ReportValue.java diff --git a/platform/configuration-store-impl/src/statistic/eventLog/FeatureUsageSettingsEvents.kt b/platform/configuration-store-impl/src/statistic/eventLog/FeatureUsageSettingsEvents.kt index 187017ee327d..98f17a979a22 100644 --- a/platform/configuration-store-impl/src/statistic/eventLog/FeatureUsageSettingsEvents.kt +++ b/platform/configuration-store-impl/src/statistic/eventLog/FeatureUsageSettingsEvents.kt @@ -6,17 +6,19 @@ import com.intellij.internal.statistic.eventLog.EventLogGroup import com.intellij.internal.statistic.eventLog.fus.FeatureUsageLogger import com.intellij.internal.statistic.utils.getPluginInfo import com.intellij.internal.statistic.utils.getProjectId +import com.intellij.openapi.components.ReportValue import com.intellij.openapi.components.State import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.util.concurrency.NonUrgentExecutor import com.intellij.util.containers.ContainerUtil +import com.intellij.serialization.MutableAccessor import com.intellij.util.xmlb.BeanBinding import org.jdom.Element import java.util.* private val LOG = Logger.getInstance("com.intellij.configurationStore.statistic.eventLog.FeatureUsageSettingsEventPrinter") -private val GROUP = EventLogGroup("settings", 3) +private val GROUP = EventLogGroup("settings", 4) private val recordedComponents: MutableSet = ContainerUtil.newConcurrentSet() private val recordedOptionNames: MutableSet = ContainerUtil.newConcurrentSet() @@ -91,20 +93,15 @@ open class FeatureUsageSettingsEventPrinter(private val recordDefault: Boolean) for (accessor in accessors) { val type = accessor.genericType if (type === Boolean::class.javaPrimitiveType) { - val value = accessor.readUnsafe(state) - val isDefault = !jdomSerializer.getDefaultSerializationFilter().accepts(accessor, state) - if (!isDefault || recordDefault) { - recordedOptionNames.add(accessor.name) - val content = HashMap() - content["component"] = componentName - content["name"] = accessor.name - content["value"] = value - if (recordDefault) { - content["default"] = isDefault - } - addProjectOptions(content, isDefaultProject, hash) - logConfig(GROUP, eventId, content) - } + logConfigValue(accessor, state, "bool", eventId, isDefaultProject, true, hash, componentName) + } + else if (type === Int::class.javaPrimitiveType || type === Long::class.javaPrimitiveType) { + val reportValue = accessor.getAnnotation(ReportValue::class.java) != null + logConfigValue(accessor, state, "int", eventId, isDefaultProject, reportValue, hash, componentName) + } + else if (type === Float::class.javaPrimitiveType || type === Double::class.javaPrimitiveType) { + val reportValue = accessor.getAnnotation(ReportValue::class.java) != null + logConfigValue(accessor, state, "float", eventId, isDefaultProject, reportValue, hash, componentName) } } @@ -113,6 +110,33 @@ open class FeatureUsageSettingsEventPrinter(private val recordDefault: Boolean) } } + private fun logConfigValue(accessor: MutableAccessor, + state: Any, + type: String, + eventId: String, + isDefaultProject: Boolean, + reportValue: Boolean, + hash: String?, + componentName: String) { + val value = accessor.readUnsafe(state) + val isDefault = !jdomSerializer.getDefaultSerializationFilter().accepts(accessor, state) + if (!isDefault || recordDefault) { + recordedOptionNames.add(accessor.name) + val content = HashMap() + content["type"] = type + content["component"] = componentName + content["name"] = accessor.name + if (reportValue) { + content["value"] = value + } + if (recordDefault) { + content["default"] = isDefault + } + addProjectOptions(content, isDefaultProject, hash) + logConfig(GROUP, eventId, content) + } + } + private fun addProjectOptions(content: HashMap, isDefaultProject: Boolean, projectHash: String?) { diff --git a/platform/configuration-store-impl/testSrc/statistics/eventLog/FeatureUsageSettingsEventsTest.kt b/platform/configuration-store-impl/testSrc/statistics/eventLog/FeatureUsageSettingsEventsTest.kt index 82cd3d776c4a..589faffeef58 100644 --- a/platform/configuration-store-impl/testSrc/statistics/eventLog/FeatureUsageSettingsEventsTest.kt +++ b/platform/configuration-store-impl/testSrc/statistics/eventLog/FeatureUsageSettingsEventsTest.kt @@ -5,11 +5,13 @@ import com.intellij.configurationStore.getStateSpec import com.intellij.configurationStore.statistic.eventLog.FeatureUsageSettingsEventPrinter import com.intellij.internal.statistic.eventLog.EventLogGroup import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.ReportValue import com.intellij.openapi.components.State import com.intellij.openapi.project.ProjectManager import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.assertions.Assertions.assertThat import com.intellij.util.xmlb.annotations.Attribute +import org.junit.Assert import org.junit.ClassRule import org.junit.Test @@ -116,8 +118,8 @@ class FeatureUsageSettingsEventsTest { val withProject = true val defaultProject = false assertThat(printer.result).hasSize(2) - assertDefaultState(printer.getOptionByName("boolOption"), "boolOption", false, withProject, defaultProject) - assertDefaultState(printer.getOptionByName("secondBoolOption"), "secondBoolOption", true, withProject, defaultProject) + assertDefaultState(printer.getOptionByName("boolOption"), "boolOption", false, "bool", withProject, defaultProject) + assertDefaultState(printer.getOptionByName("secondBoolOption"), "secondBoolOption", true, "bool", withProject, defaultProject) } @Test @@ -174,7 +176,7 @@ class FeatureUsageSettingsEventsTest { val defaultProject = false assertThat(printer.result).hasSize(2) assertInvokedRecorded(printer.getInvokedEvent(), withProject, defaultProject) - assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, withRecordDefault, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, "bool", withRecordDefault, withProject, defaultProject) } @Test @@ -200,7 +202,7 @@ class FeatureUsageSettingsEventsTest { val defaultProject = false assertThat(printer.result).hasSize(2) assertInvokedRecorded(printer.getInvokedEvent(), withProject, defaultProject) - assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, withRecordDefault, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, "bool", withRecordDefault, withProject, defaultProject) } @Test @@ -215,8 +217,8 @@ class FeatureUsageSettingsEventsTest { val withProject = true val defaultProject = false assertThat(printer.result).hasSize(2) - assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, withRecordDefault, withProject, defaultProject) - assertDefaultState(printer.getOptionByName("secondBoolOption"), "secondBoolOption", true, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, "bool", withRecordDefault, withProject, defaultProject) + assertDefaultState(printer.getOptionByName("secondBoolOption"), "secondBoolOption", true, "bool", withProject, defaultProject) } @Test @@ -232,7 +234,7 @@ class FeatureUsageSettingsEventsTest { val defaultProject = false assertThat(printer.result).hasSize(2) assertInvokedRecorded(printer.getInvokedEvent(), withProject, defaultProject) - assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, withRecordDefault, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, "bool", withRecordDefault, withProject, defaultProject) } @Suppress("SameParameterValue") @@ -248,8 +250,8 @@ class FeatureUsageSettingsEventsTest { val withProject = true val defaultProject = false assertThat(printer.result).hasSize(2) - assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, withRecordDefault, withProject, defaultProject) - assertNotDefaultState(printer.getOptionByName("secondBoolOption"), "secondBoolOption", false, withRecordDefault, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, "bool", withRecordDefault, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("secondBoolOption"), "secondBoolOption", false, "bool", withRecordDefault, withProject, defaultProject) } @Test @@ -265,8 +267,76 @@ class FeatureUsageSettingsEventsTest { val defaultProject = false assertThat(printer.result).hasSize(3) assertInvokedRecorded(printer.getInvokedEvent(), withProject, defaultProject) - assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, withRecordDefault, withProject, defaultProject) - assertNotDefaultState(printer.getOptionByName("secondBoolOption"), "secondBoolOption", false, withRecordDefault, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("boolOption"), "boolOption", true, "bool", withRecordDefault, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("secondBoolOption"), "secondBoolOption", false, "bool", withRecordDefault, withProject, defaultProject) + } + + @Test + fun `record default numerical fields in application component`() { + val component = TestComponent() + component.loadState(ComponentStateWithNumerical()) + val spec = getStateSpec(component) + val printer = TestFeatureUsageSettingsEventsPrinter(false) + printer.logConfigurationState(spec.name, component.state, null) + + val withProject = false + val defaultProject = false + Assert.assertEquals(1, printer.result.size) + assertInvokedRecorded(printer.getInvokedEvent(), withProject, defaultProject) + } + + @Test + fun `record not default numerical fields in application component`() { + val component = TestComponent() + component.loadState(ComponentStateWithNumerical(intOpt = 10, longOpt = 15, floatOpt = 5.5F, doubleOpt = 3.4)) + val spec = getStateSpec(component) + val printer = TestFeatureUsageSettingsEventsPrinter(false) + printer.logConfigurationState(spec.name, component.state, null) + + val withProject = false + val defaultProject = false + Assert.assertEquals(5, printer.result.size) + assertInvokedRecorded(printer.getInvokedEvent(), withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("integerOption"), "integerOption", null, "int", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("longOption"), "longOption", null, "int", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("floatOption"), "floatOption", null, "float", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("doubleOption"), "doubleOption", null, "float", false, withProject, defaultProject) + } + + @Test + fun `record not default numerical fields with absolute value in application component`() { + val component = TestComponent() + component.loadState(ComponentStateWithNumerical(absIntOpt = 10, absLongOpt = 15, absFloatOpt = 5.5F, absDoubleOpt = 3.4)) + val spec = getStateSpec(component) + val printer = TestFeatureUsageSettingsEventsPrinter(false) + printer.logConfigurationState(spec.name, component.state, null) + + val withProject = false + val defaultProject = false + Assert.assertEquals(5, printer.result.size) + assertInvokedRecorded(printer.getInvokedEvent(), withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("absIntegerOption"), "absIntegerOption", 10, "int", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("absLongOption"), "absLongOption", 15L, "int", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("absFloatOption"), "absFloatOption", 5.5f, "float", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("absDoubleOption"), "absDoubleOption", 3.4, "float", false, withProject, defaultProject) + } + + @Test + fun `record all not default numerical fields with absolute value in application component`() { + val component = TestComponent() + component.loadState(ComponentStateWithNumerical(absIntOpt = 10, absLongOpt = 15, absFloatOpt = 5.5F, absDoubleOpt = 3.4)) + val spec = getStateSpec(component) + val printer = TestFeatureUsageSettingsEventsPrinter(false) + printer.logConfigurationState(spec.name, component.state, null) + + val withProject = false + val defaultProject = false + Assert.assertEquals(5, printer.result.size) + assertInvokedRecorded(printer.getInvokedEvent(), withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("absIntegerOption"), "absIntegerOption", 10, "int", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("absLongOption"), "absLongOption", 15L, "int", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("absFloatOption"), "absFloatOption", 5.5f, "float", false, withProject, defaultProject) + assertNotDefaultState(printer.getOptionByName("absDoubleOption"), "absDoubleOption", 3.4, "float", false, withProject, defaultProject) } private fun assertDefaultWithoutDefaultRecording(printer: TestFeatureUsageSettingsEventsPrinter, @@ -276,14 +346,16 @@ class FeatureUsageSettingsEventsTest { assertInvokedRecorded(printer.result[0], withProject, defaultProject) } - private fun assertNotDefaultState(printer: TestFeatureUsageSettingsEventsPrinter, withRecordDefault: Boolean, withProject: Boolean, defaultProject: Boolean) { + @Suppress("SameParameterValue") + private fun assertNotDefaultState(printer: TestFeatureUsageSettingsEventsPrinter,withRecordDefault: Boolean, withProject: Boolean, defaultProject: Boolean) { assertThat(printer.result).hasSize(1) - assertNotDefaultState(printer.result[0], "boolOption", true, withRecordDefault, withProject, defaultProject) + assertNotDefaultState(printer.result[0], "boolOption", true, "bool", withRecordDefault, withProject, defaultProject) } private fun assertNotDefaultState(event: LoggedComponentStateEvents, name: String, - value: Any, + value: Any?, + type: String, withDefaultRecorded: Boolean, withProject: Boolean, defaultProject: Boolean) { @@ -292,14 +364,18 @@ class FeatureUsageSettingsEventsTest { assertThat(event.id).isEqualTo(if (withDefaultRecorded) "option" else "not.default") var size = 3 + if (value != null) size++ if (withDefaultRecorded) size++ if (withProject) size++ if (defaultProject) size++ assertThat(event.data).hasSize(size) assertThat(event.data["component"]).isEqualTo("MyTestComponent") + assertThat(event.data["type"]).isEqualTo(type) assertThat(event.data["name"]).isEqualTo(name) - assertThat(event.data["value"]).isEqualTo(value) + if (value != null) { + assertThat(event.data["value"]).isEqualTo(value) + } if (withDefaultRecorded) { assertThat(event.data["default"]).isEqualTo(false) } @@ -313,24 +389,26 @@ class FeatureUsageSettingsEventsTest { private fun assertDefaultState(printer: TestFeatureUsageSettingsEventsPrinter, withProject: Boolean, defaultProject: Boolean) { assertThat(printer.result).hasSize(1) - assertDefaultState(printer.result[0], "boolOption", false, withProject, defaultProject) + assertDefaultState(printer.result[0], "boolOption", false, "bool", withProject, defaultProject) } private fun assertDefaultState(event: LoggedComponentStateEvents, name: String, value: Any, + type: String, withProject: Boolean, defaultProject: Boolean) { assertThat(event.group.id).isEqualTo("settings") assertThat(event.group.version).isGreaterThan(0) assertThat(event.id).isEqualTo("option") - var size = 4 + var size = 5 if (withProject) size++ if (defaultProject) size++ assertThat(event.data).hasSize(size) assertThat(event.data["component"]).isEqualTo("MyTestComponent") + assertThat(event.data["type"]).isEqualTo(type) assertThat(event.data["name"]).isEqualTo(name) assertThat(event.data["value"]).isEqualTo(value) assertThat(event.data["default"]).isEqualTo(true) @@ -422,4 +500,45 @@ class FeatureUsageSettingsEventsTest { @Attribute("second-bool-value") val secondBoolOption: Boolean = secondBool } + + @Suppress("unused") + private class ComponentStateWithNumerical(intOpt: Int = 0, + longOpt: Long = 0, + floatOpt: Float = 0.0F, + doubleOpt: Double = 0.0, + absIntOpt: Int = 0, + absLongOpt: Long = 0, + absFloatOpt: Float = 0.0F, + absDoubleOpt: Double = 0.0, + bool: Boolean = false, + str: String = "string-option", + list: List = ArrayList()) : ComponentState(bool, str, list) { + @Attribute("int-option") + val integerOption: Int = intOpt + + @Attribute("long-option") + val longOption: Long = longOpt + + @Attribute("float-option") + val floatOption: Float = floatOpt + + @Attribute("double-option") + val doubleOption: Double = doubleOpt + + @Attribute("abs-int-option") + @field:ReportValue + val absIntegerOption: Int = absIntOpt + + @Attribute("abs-long-option") + @field:ReportValue + val absLongOption: Long = absLongOpt + + @Attribute("abs-float-option") + @field:ReportValue + val absFloatOption: Float = absFloatOpt + + @Attribute("abs-double-option") + @field:ReportValue + val absDoubleOption: Double = absDoubleOpt + } } \ No newline at end of file diff --git a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettingsState.kt b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettingsState.kt index 2bc193194433..1580dca369d6 100644 --- a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettingsState.kt +++ b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettingsState.kt @@ -2,6 +2,7 @@ package com.intellij.ide.ui import com.intellij.openapi.components.BaseState +import com.intellij.openapi.components.ReportValue import com.intellij.openapi.util.SystemInfo import com.intellij.ui.scale.JBUIScale import com.intellij.util.PlatformUtils @@ -34,9 +35,11 @@ class UISettingsState : BaseState() { @Deprecated("", replaceWith = ReplaceWith("NotRoamableUiOptions.fontScale")) var fontScale by property(0f) + @get:ReportValue @get:OptionTag("RECENT_FILES_LIMIT") var recentFilesLimit by property(50) + @get:ReportValue @get:OptionTag("RECENT_LOCATIONS_LIMIT") var recentLocationsLimit by property(25) @@ -47,6 +50,7 @@ class UISettingsState : BaseState() { @get:OptionTag("CONSOLE_CYCLE_BUFFER_SIZE_KB") var consoleCycleBufferSizeKb by property(1024) + @get:ReportValue @get:OptionTag("EDITOR_TAB_LIMIT") var editorTabLimit by property(10) diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/eventLog/fus/FeatureUsageEventLoggerProvider.kt b/platform/platform-impl/src/com/intellij/internal/statistic/eventLog/fus/FeatureUsageEventLoggerProvider.kt index 0bb0bfb048a4..3f9f748acd6d 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/eventLog/fus/FeatureUsageEventLoggerProvider.kt +++ b/platform/platform-impl/src/com/intellij/internal/statistic/eventLog/fus/FeatureUsageEventLoggerProvider.kt @@ -6,7 +6,7 @@ import com.intellij.internal.statistic.utils.StatisticsUploadAssistant import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.registry.Registry -class FeatureUsageEventLoggerProvider : StatisticsEventLoggerProvider("FUS", 18) { +class FeatureUsageEventLoggerProvider : StatisticsEventLoggerProvider("FUS", 19) { override fun isRecordEnabled(): Boolean { return !ApplicationManager.getApplication().isUnitTestMode && Registry.`is`("feature.usage.event.log.collect.and.upload") && diff --git a/platform/projectModel-api/src/com/intellij/openapi/components/ReportValue.java b/platform/projectModel-api/src/com/intellij/openapi/components/ReportValue.java new file mode 100644 index 000000000000..97fddcfc40aa --- /dev/null +++ b/platform/projectModel-api/src/com/intellij/openapi/components/ReportValue.java @@ -0,0 +1,19 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.components; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that an absolute value of the numerical field should be reported in statistics. + * Won't work on objects, string or enum fields. + * + * Can be used within persistent components if reportStatistics flag is enabled. + * @see State#reportStatistic() + */ +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ReportValue { +}