Skip to content

Commit 71374da

Browse files
authored
Merge pull request #2350 from digma-ai/enable-frequency-detector-for-ui-error
enable-frequency-detector-for-ui-error Closes #2349
2 parents 9647327 + a29744b commit 71374da

File tree

7 files changed

+130
-67
lines changed

7 files changed

+130
-67
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ tasks {
291291
"idea.trace.stub.index.update" to "true",
292292
"org.digma.plugin.enable.devtools" to "true",
293293
"kotlinx.coroutines.debug" to "",
294-
"org.digma.plugin.report.all.errors" to "true"
294+
"org.digma.plugin.report.all.errors" to "true",
295295

296296
// "idea.ProcessCanceledException" to "disabled"
297297

ide-common/src/main/kotlin/org/digma/intellij/plugin/analytics/ApiPerformanceMonitor.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,21 @@ class ApiPerformanceMonitor(private val project: Project) {
5252
}
5353

5454
private fun report(apiName: String, duration: Long, httpNetoTime: Long, exception: Throwable?) {
55-
if (frequencyDetector.isTooFrequentMessage(apiName)) {
55+
56+
val frequency = frequencyDetector.getMessageFrequency(apiName)
57+
if (frequency.isTooFrequent()) {
5658
return
5759
}
5860

5961
//reset the max duration before reporting, new one will be collected
6062
durations[apiName] = Pair(0L, 0L)
6163

6264
val details = mutableMapOf<String, Any>(
63-
"api name" to apiName,
65+
"frequency" to frequency.frequencySinceStart,
66+
"frequency.since.minutes" to frequency.formatDurationToMinutes(),
67+
"api.name" to apiName,
6468
"duration" to duration,
65-
"http neto" to httpNetoTime,
69+
"http.neto" to httpNetoTime,
6670
"exception" to (exception?.message ?: exception?.toString() ?: "")
6771
)
6872

ide-common/src/main/kotlin/org/digma/intellij/plugin/common/FrequencyDetector.kt

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.digma.intellij.plugin.common
22

33
import com.github.benmanes.caffeine.cache.Caffeine
44
import com.intellij.openapi.diagnostic.UntraceableException
5+
import kotlinx.datetime.Clock
56
import java.lang.reflect.Field
67
import java.util.concurrent.TimeUnit
78
import java.util.concurrent.atomic.AtomicInteger
@@ -13,36 +14,69 @@ class FrequencyDetector(cacheExpirationTime: java.time.Duration) {
1314
private val myCache = MyCache(cacheExpirationTime.toKotlinDuration())
1415

1516

17+
companion object {
18+
private val BACKTRACE_FIELD: Field? = try {
19+
val field = Throwable::class.java.getDeclaredField("backtrace")
20+
field.isAccessible = true
21+
field
22+
} catch (e: Throwable) {
23+
//don't mind its e.printStackTrace, it will just go to idea.log or to console in development, so we know about it.
24+
//but it should never happen.
25+
//never use ErrorReporter here , it will cause an endless recursion
26+
e.printStackTrace()
27+
null
28+
}
29+
30+
private fun getBacktrace(throwable: Throwable): Array<*>? {
31+
// the JVM blocks access to Throwable.backtrace via reflection sometimes
32+
val backtrace = try {
33+
BACKTRACE_FIELD?.get(throwable)
34+
} catch (e: Throwable) {
35+
//don't mind its e.printStackTrace, it will just go to idea.log or to console in development, so we know about it.
36+
//but it should never happen.
37+
//never use ErrorReporter here , it will cause an endless recursion
38+
e.printStackTrace()
39+
return null
40+
}
41+
return if (backtrace is Array<*>) backtrace else null
42+
}
43+
}
44+
1645
fun isTooFrequentMessage(message: String): Boolean {
17-
val counter = myCache.getOrCreate(message)
18-
val occurrences = counter.incrementAndGet()
19-
return occurrences > 1
46+
return getMessageFrequency(message).isTooFrequent()
47+
}
48+
49+
fun getMessageFrequency(message: String): Frequency {
50+
return myCache.getOrCreate(message)
2051
}
2152

2253
fun isTooFrequentStackTrace(message: String, stackTrace: String?): Boolean {
23-
//this method is meant to test stack trace which may be a long string.
24-
//its better not to use a long string as key, so we use the stacktrace hash.
25-
//if stacktrace is null just check isTooFrequentError
54+
return getStackTraceFrequency(message, stackTrace).isTooFrequent()
55+
}
2656

57+
fun getStackTraceFrequency(message: String, stackTrace: String?): Frequency {
2758
return stackTrace?.let {
2859
val hash = it.hashCode()
29-
val counter = myCache.getOrCreate(message, hash.toString())
30-
val occurrences = counter.incrementAndGet()
31-
occurrences > 1
32-
} ?: isTooFrequentError(message, "")
60+
myCache.getOrCreate(message, hash.toString())
61+
} ?: getErrorFrequency(message, "")
3362
}
3463

64+
3565
fun isTooFrequentError(message: String, action: String): Boolean {
36-
val counter = myCache.getOrCreate(message, action)
37-
val occurrences = counter.incrementAndGet()
38-
return occurrences > 1
66+
return getErrorFrequency(message, action).isTooFrequent()
67+
}
68+
69+
fun getErrorFrequency(message: String, action: String): Frequency {
70+
return myCache.getOrCreate(message, action)
3971
}
4072

4173
fun isTooFrequentException(message: String, t: Throwable): Boolean {
74+
return getExceptionFrequency(message, t).isTooFrequent()
75+
}
76+
77+
fun getExceptionFrequency(message: String, t: Throwable): Frequency {
4278
val hash = computeAccurateTraceHashCode(t)
43-
val counter = myCache.getOrCreate(hash, t, message)
44-
val occurrences = counter.incrementAndGet()
45-
return occurrences > 1
79+
return myCache.getOrCreate(hash, t, message)
4680
}
4781

4882
private fun computeAccurateTraceHashCode(throwable: Throwable): Int {
@@ -54,55 +88,50 @@ class FrequencyDetector(cacheExpirationTime: java.time.Duration) {
5488
return backtrace.contentDeepHashCode()
5589
}
5690

91+
}
5792

58-
private fun getBacktrace(throwable: Throwable): Array<Any>? {
59-
60-
val backtrace = try {
61-
62-
val backtraceField: Field? = com.intellij.util.ReflectionUtil.getDeclaredField(Throwable::class.java, "backtrace")
63-
if (backtraceField != null) {
64-
backtraceField.isAccessible = true
65-
backtraceField.get(throwable)
66-
} else {
67-
null
68-
}
69-
70-
} catch (e: Throwable) {
71-
null
72-
}
73-
74-
if (backtrace != null && backtrace is Array<*> && backtrace.isArrayOf<Any>()) {
75-
@Suppress("UNCHECKED_CAST")
76-
return backtrace as Array<Any>
77-
}
78-
79-
return null
8093

94+
data class Frequency(val frequencyInLastPeriod: Int, val frequencySinceStart: Int, val duration: Duration) {
95+
fun isTooFrequent(): Boolean {
96+
return frequencyInLastPeriod > 1
8197
}
8298

99+
fun formatDurationToMinutes(): Long {
100+
return duration.inWholeMinutes
101+
}
83102
}
84103

85104

86105
private class MyCache(cacheExpirationTime: Duration) {
106+
107+
private val startTime: kotlinx.datetime.Instant = Clock.System.now()
108+
87109
private val cache = Caffeine.newBuilder()
88110
.maximumSize(1000)
89-
//expireAfterAccess means we don't send the error as long as it keeps occurring until it is quite for this time,
90-
//if it reappears send it again
91-
// .expireAfterAccess(10, TimeUnit.MINUTES)
92-
//expireAfterWrite mean the error will be sent in fixed intervals.
93-
// .expireAfterWrite(24, TimeUnit.HOURS)
94111
.expireAfterWrite(cacheExpirationTime.inWholeMinutes, TimeUnit.MINUTES)
95112
.build<String, AtomicInteger>()
96113

97-
fun getOrCreate(hash: Int, t: Throwable, message: String): AtomicInteger {
98-
return cache.get("$hash:$t:$message") { AtomicInteger() }
114+
private val frequencies = Caffeine.newBuilder()
115+
.maximumSize(1000)
116+
.build<String, AtomicInteger>()
117+
118+
119+
fun getOrCreate(hash: Int, t: Throwable, message: String): Frequency {
120+
return getFrequency("$hash:$t:$message")
121+
}
122+
123+
fun getOrCreate(message: String, action: String): Frequency {
124+
return getFrequency("$message:$action")
99125
}
100126

101-
fun getOrCreate(message: String, action: String): AtomicInteger {
102-
return cache.get("$message:$action") { AtomicInteger() }
127+
fun getOrCreate(message: String): Frequency {
128+
return getFrequency(message)
103129
}
104130

105-
fun getOrCreate(message: String): AtomicInteger {
106-
return cache.get(message) { AtomicInteger() }
131+
private fun getFrequency(key: String): Frequency {
132+
val frequencyInLastPeriod = cache.get(key) { AtomicInteger() }.incrementAndGet()
133+
val frequencySinceStart = frequencies.get(key) { AtomicInteger() }.incrementAndGet()
134+
return Frequency(frequencyInLastPeriod, frequencySinceStart, Clock.System.now().minus(startTime))
107135
}
136+
108137
}

ide-common/src/main/kotlin/org/digma/intellij/plugin/errorreporting/ErrorReporter.kt

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ open class ErrorReporter {
100100
/*
101101
try not to use this method and always send the project reference if available.
102102
*/
103-
open fun reportError(message: String, t: Throwable, extraDetails: Map<String, String>) {
103+
open fun reportError(message: String, t: Throwable, extraDetails: Map<String, Any>) {
104104
reportError(findActiveProject(), message, t, extraDetails)
105105
}
106106

@@ -109,7 +109,7 @@ open class ErrorReporter {
109109
}
110110

111111

112-
open fun reportUIError(project: Project?, message: String, stackTrace: String?, details: Map<String, Any>, useFrequencyDetector: Boolean = true) {
112+
open fun reportUIError(project: Project?, message: String, stackTrace: String?, details: Map<String, Any>) {
113113

114114
try {
115115
if (message.isEmpty() && stackTrace.isNullOrEmpty()) {
@@ -120,15 +120,21 @@ open class ErrorReporter {
120120
return
121121
}
122122

123-
if (useFrequencyDetector && frequencyDetector.isTooFrequentStackTrace(message, stackTrace)) {
123+
val frequency = frequencyDetector.getStackTraceFrequency(message, stackTrace)
124+
if (frequency.isTooFrequent()) {
124125
return
125126
}
126127

127128
val projectToUse = project ?: findActiveProject()
128129

129130
projectToUse?.let {
130131
if (it.isDisposed) return
131-
ActivityMonitor.getInstance(it).registerError(null, message, ensureDetailsWithSeverity(details))
132+
133+
val detailsWithFrequency = details.toMutableMap()
134+
detailsWithFrequency["frequency"] = frequency.frequencySinceStart
135+
detailsWithFrequency["frequency.since.minutes"] = frequency.formatDurationToMinutes()
136+
137+
ActivityMonitor.getInstance(it).registerError(null, message, ensureDetailsWithSeverity(detailsWithFrequency))
132138
}
133139
} catch (e: Exception) {
134140
Log.warnWithException(logger, e, "error in error reporter")
@@ -144,7 +150,8 @@ open class ErrorReporter {
144150
open fun reportError(project: Project?, message: String, action: String, details: Map<String, Any>) {
145151

146152
try {
147-
if (frequencyDetector.isTooFrequentError(message, action)) {
153+
val frequency = frequencyDetector.getErrorFrequency(message, action)
154+
if (frequency.isTooFrequent()) {
148155
return
149156
}
150157

@@ -155,6 +162,8 @@ open class ErrorReporter {
155162

156163
val detailsToSend = details.toMutableMap()
157164
detailsToSend["action"] = action
165+
detailsToSend["frequency"] = frequency.frequencySinceStart
166+
detailsToSend["frequency.since.minutes"] = frequency.formatDurationToMinutes()
158167

159168
ActivityMonitor.getInstance(it).registerError(null, message, ensureDetailsWithSeverity(detailsToSend))
160169
}
@@ -164,7 +173,7 @@ open class ErrorReporter {
164173
}
165174

166175

167-
open fun reportError(project: Project?, message: String, throwable: Throwable, details: Map<String, String>) {
176+
open fun reportError(project: Project?, message: String, throwable: Throwable, details: Map<String, Any>) {
168177

169178
try {
170179
//many times the exception is no-connection exception, and that may happen too many times.
@@ -174,15 +183,22 @@ open class ErrorReporter {
174183
return
175184
}
176185

177-
if (frequencyDetector.isTooFrequentException(message, throwable)) {
186+
val frequency = frequencyDetector.getExceptionFrequency(message, throwable)
187+
if (frequency.isTooFrequent()) {
178188
return
179189
}
180190

191+
181192
val projectToUse = project ?: findActiveProject()
182193

183194
projectToUse?.let {
184195
if (it.isDisposed) return
185-
ActivityMonitor.getInstance(it).registerError(throwable, message, ensureDetailsWithSeverity(details))
196+
197+
val detailsWithFrequency = details.toMutableMap()
198+
detailsWithFrequency["frequency"] = frequency.frequencySinceStart
199+
detailsWithFrequency["frequency.since.minutes"] = frequency.formatDurationToMinutes()
200+
201+
ActivityMonitor.getInstance(it).registerError(throwable, message, ensureDetailsWithSeverity(detailsWithFrequency))
186202
}
187203
} catch (e: Exception) {
188204
Log.warnWithException(logger, e, "error in error reporter")
@@ -199,15 +215,16 @@ open class ErrorReporter {
199215
) {
200216

201217
try {
202-
if (frequencyDetector.isTooFrequentException(message, exception)) {
218+
val frequency = frequencyDetector.getExceptionFrequency(message, exception)
219+
if (frequency.isTooFrequent()) {
203220
return
204221
}
205222

206223
if (!isProjectValid(project)) {
207224
return
208225
}
209226

210-
ActivityMonitor.getInstance(project).registerAnalyticsServiceError(exception, message, methodName, isConnectionException)
227+
ActivityMonitor.getInstance(project).registerAnalyticsServiceError(exception, message, methodName, isConnectionException, frequency)
211228

212229
} catch (e: Exception) {
213230
Log.warnWithException(logger, e, "error in error reporter")
@@ -216,6 +233,7 @@ open class ErrorReporter {
216233

217234

218235
open fun reportBackendError(project: Project?, message: String, action: String) {
236+
219237
if (frequencyDetector.isTooFrequentError(message, action)) {
220238
return
221239
}

ide-common/src/main/kotlin/org/digma/intellij/plugin/posthog/ActivityMonitor.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import kotlinx.coroutines.launch
1616
import kotlinx.datetime.toJavaInstant
1717
import org.digma.intellij.plugin.analytics.BackendConnectionMonitor
1818
import org.digma.intellij.plugin.common.ExceptionUtils
19+
import org.digma.intellij.plugin.common.Frequency
1920
import org.digma.intellij.plugin.common.UniqueGeneratedUserId
2021
import org.digma.intellij.plugin.common.objectToJson
2122
import org.digma.intellij.plugin.execution.DIGMA_INSTRUMENTATION_ERROR
@@ -425,7 +426,13 @@ class ActivityMonitor(private val project: Project, cs: CoroutineScope) : Dispos
425426
}
426427

427428

428-
fun registerAnalyticsServiceError(exception: Throwable, message: String, methodName: String, isConnectionException: Boolean) {
429+
fun registerAnalyticsServiceError(
430+
exception: Throwable,
431+
message: String,
432+
methodName: String,
433+
isConnectionException: Boolean,
434+
frequency: Frequency
435+
) {
429436

430437
try {
431438

@@ -459,7 +466,9 @@ class ActivityMonitor(private val project: Project, cs: CoroutineScope) : Dispos
459466
"ide.build" to ideBuildNumber,
460467
"plugin.version" to pluginVersion,
461468
"server.version" to serverInfo?.applicationVersion.toString(),
462-
"user.type" to if (isDevUser) "internal" else "external"
469+
"user.type" to if (isDevUser) "internal" else "external",
470+
"frequency" to frequency.frequencySinceStart,
471+
"frequency.since.minutes" to frequency.formatDurationToMinutes()
463472
)
464473
)
465474
} catch (e: Exception) {

ide-common/src/main/kotlin/org/digma/intellij/plugin/scheduling/schedulers.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,12 +560,15 @@ class ScheduledTasksPerformanceMonitor {
560560
return
561561
}
562562

563-
if (frequencyDetector.isTooFrequentMessage(taskName)) {
563+
val frequency = frequencyDetector.getMessageFrequency(taskName)
564+
if (frequency.isTooFrequent()) {
564565
return
565566
}
566567

567568
findActiveProject()?.let {
568569
val details = mutableMapOf<String, Any>(
570+
"frequency" to frequency.frequencySinceStart,
571+
"frequency.since.minutes" to frequency.formatDurationToMinutes(),
569572
"task.name" to taskName,
570573
"duration" to duration
571574
)

src/main/kotlin/org/digma/intellij/plugin/ui/jcef/BaseMessageRouterHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ abstract class BaseMessageRouterHandler(protected val project: Project) : Common
151151
pl.data?.let {
152152
val stackTrace = pl.data["exception.stack-trace"] as String?
153153
val message = pl.data["message"] as String
154-
ErrorReporter.getInstance().reportUIError(project, message, stackTrace, pl.data, false)
154+
ErrorReporter.getInstance().reportUIError(project, message, stackTrace, pl.data)
155155
}
156156
} else {
157157
if (pl.data == null) {

0 commit comments

Comments
 (0)