Files
alfred-android/navi-alfred/src/main/java/com/navi/alfred/utils/EventUtils.kt
2023-12-29 14:56:59 +00:00

584 lines
25 KiB
Kotlin

package com.navi.alfred.utils
import androidx.annotation.WorkerThread
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.navi.alfred.AlfredManager
import com.navi.alfred.db.model.AnalyticsEvent
import com.navi.alfred.db.model.ApiMetricHelper
import com.navi.alfred.db.model.FailureEvent
import com.navi.alfred.db.model.NegativeCase
import com.navi.alfred.dispatcher.AlfredDispatcher
import com.navi.alfred.model.AnalyticsRequest
import com.navi.alfred.model.BaseAttribute
import com.navi.alfred.model.DeviceAttributes
import com.navi.alfred.model.EventAttribute
import com.navi.alfred.model.EventMetricRequest
import com.navi.alfred.model.Failure
import com.navi.alfred.model.FailureAttributes
import com.navi.alfred.model.FailureRequest
import com.navi.alfred.model.MetricAttribute
import com.navi.alfred.model.SessionEventAttribute
import com.navi.alfred.model.SessionRequest
import com.navi.alfred.utils.AlfredConstants.API_ERROR
import com.navi.alfred.utils.AlfredConstants.DEFAULT_INGEST_METRIC_URL
import com.navi.alfred.utils.AlfredConstants.DEFAULT_SEND_CRASH_URL
import com.navi.alfred.utils.AlfredConstants.DEFAULT_SEND_EVENT_POST_URL
import com.navi.alfred.utils.AlfredConstants.DEFAULT_SEND_FAILURE_POST_URL
import com.navi.alfred.utils.AlfredConstants.DEFAULT_SEND_SESSION_POST_URL
import com.navi.alfred.utils.AlfredConstants.INGEST_CRASH_FAILURE
import com.navi.alfred.utils.AlfredConstants.INGEST_EVENT_FAILURE
import com.navi.alfred.utils.AlfredConstants.INGEST_METRIC_FAILURE
import com.navi.alfred.utils.AlfredConstants.INGEST_SESSION_FAILURE
import com.navi.alfred.utils.AlfredConstants.POST_METHOD
import com.navi.alfred.worker.AddEventTask
import com.navi.alfred.worker.AddFailureTask
import com.navi.alfred.worker.AddMetricTask
import com.navi.alfred.worker.AddNegativeCase
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.withLock
import java.lang.reflect.Type
import java.util.UUID
import kotlin.concurrent.fixedRateTimer
internal suspend fun sendEventsToServer(
clientTs: Long? = null,
snapshotPerSecond: Int? = null,
workManagerFlow: Boolean? = false
): Boolean {
if (workManagerFlow == true || (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getEnableRecordingStatus())) {
try {
var baseAttributes = BaseAttribute()
if (workManagerFlow == true) {
baseAttributes = BaseAttribute(
clientTs = clientTs,
snapshotPerSecond = snapshotPerSecond
)
AlfredManager.analyticsDao = AlfredManager.alfredDataBase.analyticsDao()
}
AlfredManager.mutex.withLock {
val analyticsEvents =
AlfredManager.analyticsDao.fetchEvents(AlfredManager.config.getEventBatchSize())
if (analyticsEvents.isNotEmpty()) {
try {
val detailsList = analyticsEvents.map { it.details }
val listType: Type =
object : TypeToken<ArrayList<EventAttribute?>?>() {}.type
val events: ArrayList<EventAttribute> =
Gson().fromJson(detailsList.toString(), listType)
if (events.size > 0) {
val request = AnalyticsRequest(
baseAttribute = baseAttributes,
events = events
)
val response = AlfredManager.networkRepository.sendEvents(
AlfredManager.config.getPostUrl(),
AlfredManager.config.getApiKey(),
request
)
return if (
response.isSuccessful && response.code() == AlfredConstants.CODE_API_SUCCESS) {
AlfredManager.analyticsDao.deleteEvents(analyticsEvents.map { it.eventId })
true
} else {
val zipNamesList: MutableList<String> = mutableListOf()
val eventIdList: MutableList<String> = mutableListOf()
events.forEach { event ->
if (!zipNamesList.contains(event.zipName)) {
zipNamesList.add(event.zipName ?: "")
}
eventIdList.add(event.eventId ?: "")
}
val failureEvent = buildFailureEvent(
errorType = API_ERROR,
requestUrl = DEFAULT_SEND_EVENT_POST_URL,
requestMethod = POST_METHOD,
zipName = zipNamesList,
errorStatusCode = response.code().toLong(),
errorMessage = response.message(),
errorName = INGEST_EVENT_FAILURE,
clientTs = request.baseAttribute?.clientTs,
eventIdList = eventIdList
)
AlfredDispatcher.addTaskToQueue(
AddFailureTask(
failureEvent,
context = AlfredManager.applicationContext
)
)
if (response.code() == AlfredConstants.CODE_API_BAD_REQUEST) {
AlfredManager.analyticsDao.deleteEvents(analyticsEvents.map { it.eventId })
true
} else {
false
}
}
} else {
false
}
} catch (e: Exception) {
return false
}
} else {
return false
}
}
} catch (e: Exception) {
e.log()
return false
}
return false
}
return false
}
internal suspend fun sendNegativeCaseToServer(
clientTs: Long? = null,
snapshotPerSecond: Int? = null,
workManagerFlow: Boolean? = false
): Boolean {
if (workManagerFlow == true || (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getEnableRecordingStatus())) {
try {
var baseAttributes = BaseAttribute()
if (workManagerFlow == true) {
baseAttributes = BaseAttribute(
clientTs = clientTs,
snapshotPerSecond = snapshotPerSecond
)
AlfredManager.negativeCaseDao = AlfredManager.alfredDataBase.negativeCaseDao()
}
AlfredManager.mutex.withLock {
val negativeEvents =
AlfredManager.negativeCaseDao.fetchNegativeCase(AlfredManager.config.getEventBatchSize())
if (negativeEvents.isNotEmpty()) {
try {
val detailsList = negativeEvents.map { it.details }
val listType: Type =
object : TypeToken<ArrayList<EventAttribute?>?>() {}.type
val events: ArrayList<EventAttribute> =
Gson().fromJson(detailsList.toString(), listType)
if (events.size > 0) {
val request =
AnalyticsRequest(
baseAttribute = baseAttributes,
events = events
)
val response = AlfredManager.networkRepository.sendNegativeCase(
DEFAULT_SEND_CRASH_URL,
AlfredManager.config.getApiKey(),
request
)
return if (
response.isSuccessful && response.code() == AlfredConstants.CODE_API_SUCCESS
) {
AlfredManager.negativeCaseDao.deleteNegativeCase(negativeEvents.map { it.negativeCaseId })
true
} else {
val failureEvent = buildFailureEvent(
errorType = API_ERROR,
requestUrl = DEFAULT_SEND_CRASH_URL,
requestMethod = POST_METHOD,
errorStatusCode = response.code().toLong(),
errorMessage = response.message(),
errorName = INGEST_CRASH_FAILURE
)
AlfredDispatcher.addTaskToQueue(
AddFailureTask(
failureEvent,
context = AlfredManager.applicationContext
)
)
if (response.code() == AlfredConstants.CODE_API_BAD_REQUEST) {
AlfredManager.negativeCaseDao.deleteNegativeCase(negativeEvents.map { it.negativeCaseId })
true
} else {
false
}
}
} else {
false
}
} catch (e: Exception) {
return false
}
} else {
return false
}
}
} catch (e: Exception) {
e.log()
return false
}
return false
}
return false
}
internal suspend fun sendFailureEventsToServer(
workManagerFlow: Boolean? = false
): Boolean {
if (workManagerFlow == true || (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getEnableRecordingStatus())) {
try {
if (workManagerFlow == true) {
AlfredManager.failureEventDao = AlfredManager.alfredDataBase.failureEventDao()
}
AlfredManager.mutex.withLock {
val failureEvents =
AlfredManager.failureEventDao.fetchFailureEvents(AlfredManager.config.getFailureEventBatchSize())
if (failureEvents.isNotEmpty()) {
try {
val detailsList = failureEvents.map { it.details }
val listType: Type =
object : TypeToken<ArrayList<Failure?>?>() {}.type
val events: ArrayList<Failure> =
Gson().fromJson(detailsList.toString(), listType)
if (events.size > 0) {
val failureAttributes = FailureAttributes(sessionId = events[0].sessionId)
val request =
FailureRequest(
failureAttributes = failureAttributes,
events = events
)
val response = AlfredManager.networkRepository.sendFailure(
DEFAULT_SEND_FAILURE_POST_URL,
AlfredManager.config.getApiKey(),
request
)
return if (
(response.isSuccessful && response.code() == AlfredConstants.CODE_API_SUCCESS) or
(response.code() == AlfredConstants.CODE_API_BAD_REQUEST)
) {
AlfredManager.failureEventDao.deleteFailureEvents(failureEvents.map { it.eventId })
true
} else {
false
}
} else {
false
}
} catch (e: Exception) {
return false
}
} else {
return false
}
}
} catch (e: Exception) {
e.log()
return false
}
return false
}
return false
}
internal fun sendAlfredSessionEvent(
dumpFlow: Boolean = false,
index: Int? = null,
currentZipName: String? = null,
latestScreenshotTimestamp: Long? = null
) {
var request: SessionRequest? = null
if (dumpFlow) {
var clientTs: Long? = null
var sessionTimeStamp: Long? = null
var sessionId: String? = null
if (index != null) {
val zipFileDetail = AlfredManager.zipFileDetails[index]
clientTs = zipFileDetail.eventStartRecordingTime
sessionTimeStamp = zipFileDetail.sessionStartRecordingTime
sessionId = zipFileDetail.alfredSessionId
} else {
clientTs = AlfredManager.eventStartRecordingTimeForCrash
sessionTimeStamp = AlfredManager.sessionStartRecordingTimeForCrash
sessionId = AlfredManager.sessionIdForCrash
}
request =
SessionRequest(
base_attribute =
BaseAttribute(
sessionId = sessionId,
eventTimeStamp = AlfredManager.config.getEventTimeStamp(),
clientTs = clientTs,
sessionTimeStamp = sessionTimeStamp,
latestScreenshotTimestamp = latestScreenshotTimestamp
),
session_upload_event_attributes =
SessionEventAttribute(
eventId = currentZipName,
beginningDeviceAttributes = DeviceAttributes(),
endDeviceAttributes = DeviceAttributes()
)
)
} else {
request =
SessionRequest(
base_attribute =
BaseAttribute(
sessionId = AlfredManager.config.getAlfredSessionId(),
eventTimeStamp = AlfredManager.config.getEventTimeStamp(),
clientTs = AlfredManager.config.getEventStartRecordingTime(),
latestScreenshotTimestamp = latestScreenshotTimestamp,
sessionTimeStamp = AlfredManager.config.getSessionStartRecordingTime()
),
session_upload_event_attributes =
SessionEventAttribute(
eventId = currentZipName,
beginningDeviceAttributes =
DeviceAttributes(
battery = AlfredManager.config.batteryPercentageBeforeEventStart,
cpu = AlfredManager.config.cpuUsageBeforeEventStart,
memory = AlfredManager.config.memoryUsageBeforeEventStart,
storage = AlfredManager.config.storageUsageBeforeEventStart
),
endDeviceAttributes =
DeviceAttributes(
battery = AlfredManager.config.getBatteryPercentage(),
cpu = AlfredManager.config.getCpuUsage(),
memory = AlfredManager.config.getMemoryUsagePercentage(),
storage = AlfredManager.config.getStorageUsage()
)
)
)
}
AlfredManager.coroutineScope.launch {
val response = AlfredManager.networkRepository.sendSession(
DEFAULT_SEND_SESSION_POST_URL,
AlfredManager.config.getApiKey(),
request
)
if (response.isSuccessful && response.code() == AlfredConstants.CODE_API_SUCCESS) {
if (!dumpFlow) {
AlfredManager.config.setEventStartRecordingTime(true)
handleDeviceAttributes()
} else {
index?.let { index -> AlfredManager.zipDetailsDao.deleteZipFileDetail(AlfredManager.zipFileDetails[index].id) }
if (AlfredManager.workFailureData.size > 0) {
AlfredManager.workFailureData.removeAt(0)
}
}
} else {
val failureEvent = buildFailureEvent(
errorType = API_ERROR,
requestUrl = DEFAULT_SEND_SESSION_POST_URL,
requestMethod = POST_METHOD,
zipName = request.session_upload_event_attributes.eventId?.let { listOf(it) },
errorStatusCode = response.code().toLong(),
errorMessage = response.message(),
errorName = INGEST_SESSION_FAILURE,
clientTs = request.base_attribute.clientTs
)
AlfredDispatcher.addTaskToQueue(
AddFailureTask(
failureEvent,
context = AlfredManager.applicationContext
)
)
}
}
return
}
internal suspend fun sendIngestMetric(
clientTs: Long? = null,
snapshotPerSecond: Int? = null,
workManagerFlow: Boolean? = false
): Boolean {
if ((workManagerFlow == true) || (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getEnableRecordingStatus())) {
try {
var baseAttributes = BaseAttribute()
if (workManagerFlow == true) {
baseAttributes = BaseAttribute(
clientTs = clientTs,
snapshotPerSecond = snapshotPerSecond
)
AlfredManager.apiMetricDao = AlfredManager.alfredDataBase.apiMetricDao()
}
AlfredManager.mutex.withLock {
val metricEventList =
AlfredManager.apiMetricDao.fetchApiMetric(AlfredManager.config.getEventBatchSize())
if (metricEventList.isNotEmpty()) {
try {
val detailsList = metricEventList.map { it.metric }
val listType: Type =
object : TypeToken<ArrayList<MetricAttribute?>?>() {}.type
val events: ArrayList<MetricAttribute> =
Gson().fromJson(detailsList.toString(), listType)
if (events.size > 0) {
val request =
EventMetricRequest(
baseAttribute = baseAttributes,
metricsAttribute = events
)
val response =
AlfredManager.networkRepository.eventMetric(
DEFAULT_INGEST_METRIC_URL,
AlfredManager.config.getApiKey(),
metricRequestBody = request
)
return if (
response.isSuccessful && response.code() == AlfredConstants.CODE_API_SUCCESS
) {
AlfredManager.apiMetricDao.deleteApiMetric(metricEventList.map { it.id })
true
} else {
val failureEvent = buildFailureEvent(
errorType = API_ERROR,
requestUrl = DEFAULT_INGEST_METRIC_URL,
requestMethod = POST_METHOD,
errorStatusCode = response.code().toLong(),
errorMessage = response.message(),
errorName = INGEST_METRIC_FAILURE
)
AlfredDispatcher.addTaskToQueue(
AddFailureTask(
failureEvent,
context = AlfredManager.applicationContext
)
)
if (response.code() == AlfredConstants.CODE_API_BAD_REQUEST) {
AlfredManager.apiMetricDao.deleteApiMetric(metricEventList.map { it.id })
true
} else {
false
}
}
}
} catch (e: Exception) {
return false
}
} else {
return false
}
}
} catch (e: Exception) {
e.log()
return false
}
return false
}
return false
}
internal fun startSyncEvents() {
AlfredManager.timer =
fixedRateTimer(
AlfredConstants.TIMER_THREAD_NAME,
false,
AlfredConstants.DEFAULT_INITIAL_DELAY,
AlfredManager.config.getEventsDelayInMilliseconds()
) {
runBlocking {
AddMetricTask.resetEventCount()
sendIngestMetric()
AddEventTask.resetEventCount()
sendEventsToServer()
AddNegativeCase.resetEventCount()
sendNegativeCaseToServer()
AddFailureTask.resetEventCount()
sendFailureEventsToServer()
}
}
}
@WorkerThread
internal fun buildEvent(
eventName: String,
properties: HashMap<String, String>? = null,
screenName: String? = null,
moduleName: String? = null
): AnalyticsEvent {
val timeStamp = System.currentTimeMillis()
val eventData =
EventAttribute(
eventId = UUID.randomUUID().toString(),
eventName = eventName,
eventType = eventName,
eventTimestamp = timeStamp,
attributes = properties,
sessionId = AlfredManager.config.getAlfredSessionId(),
screenName = screenName,
moduleName = moduleName,
fragmentList = getFragmentList(),
zipName = AlfredManager.config.getAlfredEventId(),
screenShotTimeStamp = AlfredManager.config.getLatestScreenshotTimestamp()
)
return AnalyticsEvent(timeStamp, Gson().toJson(eventData))
}
@WorkerThread
internal fun buildNegativeCaseEvent(
eventName: String,
properties: HashMap<String, String>? = null,
screenName: String? = null,
moduleName: String? = null
): NegativeCase {
val timeStamp = System.currentTimeMillis()
val eventData =
EventAttribute(
eventId = UUID.randomUUID().toString(),
eventName = eventName,
eventType = eventName,
eventTimestamp = timeStamp,
attributes = properties,
sessionId = AlfredManager.config.getAlfredSessionId(),
screenName = screenName,
moduleName = moduleName,
fragmentList = getFragmentList(),
zipName = AlfredManager.config.getAlfredEventId()
)
return NegativeCase(timeStamp, Gson().toJson(eventData))
}
@WorkerThread
internal fun buildFailureEvent(
errorType: String,
requestUrl: String,
requestMethod: String,
zipName: List<String>? = null,
errorStatusCode: Long,
errorMessage: String? = "",
errorName: String? = null,
clientTs: Long? = null,
eventIdList: List<String>? = null
): FailureEvent {
val timeStamp = System.currentTimeMillis()
val eventData =
Failure(
errorType = errorType,
requestUrl = requestUrl,
requestMethod = requestMethod,
zipName = zipName,
errorStatusCode = errorStatusCode,
errorMessage = errorMessage,
errorName = errorName,
clientTs = clientTs,
eventIdList = eventIdList
)
return FailureEvent(timeStamp, Gson().toJson(eventData))
}
@WorkerThread
internal fun buildAppPerformanceEvent(
eventName: String,
eventType: String,
attribute: HashMap<String, Any>? = null
): ApiMetricHelper {
val timeStamp = System.currentTimeMillis()
val metricData =
MetricAttribute(
eventId = AlfredManager.config.getAlfredEventId(),
eventName = eventName,
eventType = eventType,
sessionId = AlfredManager.config.getAlfredSessionId(),
attributes = attribute,
screenName = AlfredManager.currentScreen.name,
moduleName = AlfredManager.currentModuleName,
fragmentList = getFragmentList()
)
return ApiMetricHelper(timeStamp, Gson().toJson(metricData))
}