Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop merge into main #297

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f12ccdb
Merge pull request #295 from newrelic/main
cthomas-newrelic Oct 17, 2024
d3c7f2f
Update workflowCleanup.yml (#296)
cthomas-newrelic Oct 18, 2024
e56e0d6
chore: refactor AEI code into its own package
cthomas-newrelic Oct 24, 2024
6f63430
Merge pull request #298 from newrelic/feature/refactorAEISource
cthomas-newrelic Oct 24, 2024
87a9cbf
[NR-331312] Add sessionId mapper to manage historic session Ids
cthomas-newrelic Oct 24, 2024
ed9acd3
[NR-331312] Integrate session mapper into ApplicationExitMonitor
cthomas-newrelic Oct 24, 2024
165b3eb
[NR-331312] Reconcile artifacts with AEI record set returned by ART
cthomas-newrelic Oct 24, 2024
e5635dc
[NR-331312] Add session ID of historic process as AEI event attribute
cthomas-newrelic Oct 26, 2024
c8626ca
chore: add file backed payload implementation
cthomas-newrelic Oct 28, 2024
1643b45
Merge branch 'develop' into feature/nr331312-aeiSessionMapper
cthomas-newrelic Oct 28, 2024
dba45d1
fix: increase timeout on Android `check` task (#303)
cthomas-newrelic Oct 28, 2024
17ac2c8
Merge branch 'develop' into feature/nr331312-aeiSessionMapper
cthomas-newrelic Oct 28, 2024
c86e0ed
Merge pull request #299 from newrelic/feature/nr331312-aeiSessionMapper
cthomas-newrelic Oct 28, 2024
a2247fc
[NR-315570] Add AEI trace model
cthomas-newrelic Oct 28, 2024
f644a97
chore: cleanup tests
cthomas-newrelic Oct 31, 2024
bb25f63
Merge pull request #305 from newrelic/feature/testCleanup
cthomas-newrelic Oct 31, 2024
f6b7a25
[NR-293384] Add AEI trace reporter and upload components
cthomas-newrelic Oct 30, 2024
45a6e7d
[NR-293384] Integrate AEIReport/Sender into ApplicationExitMonitor
cthomas-newrelic Oct 31, 2024
4d07986
[NR-293384] Integration refinements
cthomas-newrelic Nov 4, 2024
ae4ccb6
Merge pull request #306 from newrelic/feature/nr293384-aeiTraceReporting
cthomas-newrelic Nov 4, 2024
83ca5a2
[NR-293384] Update header request
cthomas-newrelic Nov 6, 2024
d0ac932
[NR-293384] Compress artifact on 413 response
cthomas-newrelic Nov 7, 2024
946f1ba
Merge pull request #307 from newrelic/feature/nr293384-errorsProtocol
cthomas-newrelic Nov 8, 2024
4876c52
Null Throwable are generating repetitive logs in Console. (#309)
ndesai-newrelic Nov 12, 2024
a7c6252
[NR-337308] Add realAgentId to historic AEI metadata (#308)
cthomas-newrelic Nov 13, 2024
ea7f52a
[NR-341992] null pointer fix
ywang-nr Nov 20, 2024
45c3f97
Merge pull request #313 from newrelic/feature/nr341992-nullPointerFix
ywang-nr Nov 21, 2024
18edca6
common attributes for logs request (#311)
ndesai-newrelic Nov 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: set up JDK 11
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/workflowCleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: |
echo n | .github/bin/trimWorkflowRuns
echo y | .github/bin/trimWorkflowRuns
shell: bash
env:
GH_TOKEN: ${{ github.token }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package com.newrelic.agent.android;

import com.newrelic.agent.android.aei.ApplicationExitConfiguration;
import com.newrelic.agent.android.analytics.AnalyticsAttributeStore;
import com.newrelic.agent.android.analytics.AnalyticsEventStore;
import com.newrelic.agent.android.crash.CrashStore;
Expand Down Expand Up @@ -206,7 +207,7 @@ public void setApplicationFrameworkVersion(String applicationFrameworkVersion) {
this.applicationFrameworkVersion = applicationFrameworkVersion;
}

protected String provideSessionId() {
public String provideSessionId() {
sessionID = UUID.randomUUID().toString();
return sessionID;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
package com.newrelic.agent.android;

import com.google.gson.annotations.SerializedName;
import com.newrelic.agent.android.harvest.Harvest;
import com.newrelic.agent.android.aei.ApplicationExitConfiguration;
import com.newrelic.agent.android.harvest.HarvestConfigurable;
import com.newrelic.agent.android.harvest.HarvestConfiguration;
import com.newrelic.agent.android.harvest.HarvestLifecycleAware;
import com.newrelic.agent.android.logging.LogLevel;
import com.newrelic.agent.android.logging.LogReportingConfiguration;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright (c) 2024. New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.newrelic.agent.android.aei;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.newrelic.agent.android.logging.AgentLogManager;
import com.newrelic.agent.android.util.Streams;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class AEISessionMapper {

static final Gson gson = new GsonBuilder().create();

final File mapStore;
final Map<Integer, AEISessionMeta> mapper;

public AEISessionMapper(File mapStore) {
this.mapStore = mapStore;
this.mapper = new HashMap<>();
if (mapStore.exists()) {
load();
}
}

public AEISessionMapper put(int pid, AEISessionMeta model) {
if (model != null && !(model.sessionId == null || model.sessionId.isEmpty())) {
mapper.putIfAbsent(pid, model);
} else {
AgentLogManager.getAgentLog().debug("Refusing to store null or empty session model for pid[" + pid + "]");
}

return this;
}

public AEISessionMeta get(int pid) {
return mapper.getOrDefault(pid, null);
}

public String getSessionId(int pid) {
AEISessionMeta model = get(pid);
return model == null ? "" : model.sessionId;
}

public int getRealAgentID(int pid) {
AEISessionMeta model = get(pid);
return model == null ? 0 : model.realAgentId;
}

public String getOrDefault(int pid, String defaultSessionId) {
AEISessionMeta model = get(pid);
return (model == null || model.sessionId == null || model.sessionId.isEmpty())
? defaultSessionId : model.sessionId;
}

@SuppressWarnings("unchecked")
public AEISessionMapper load() {
if (mapStore.exists() && mapStore.canRead()) {
try {
String storeData = Streams.slurpString(mapStore, StandardCharsets.UTF_8.toString());
final Type gtype = new TypeToken<Map<Integer, AEISessionMeta>>(){}.getType();
Map map = gson.fromJson(storeData, gtype);

map.forEach((key, val) -> mapper.putIfAbsent((Integer) key, (AEISessionMeta) val));

} catch (Exception e) {
AgentLogManager.getAgentLog().error("Cannot read session ID mapper: " + e);
}
} else {
AgentLogManager.getAgentLog().debug("Cannot read session ID mapper: file does not exist or is unreadable");
}

return this;
}

public boolean flush() {
if (mapper.isEmpty()) {
mapStore.delete();
} else {
try (BufferedWriter os = Streams.newBufferedFileWriter(mapStore)) {
os.write(gson.toJson(mapper));
os.flush();

} catch (IOException e) {
AgentLogManager.getAgentLog().error("Cannot write session ID mapping file: " + e);
}
}
return mapStore.exists() && mapStore.canRead();
}

public void clear() {
mapper.clear();
}

public void delete() {
if (mapStore.exists()) {
mapStore.delete();
}
}

public void erase(int pid) {
mapper.remove(pid);
}

public int size() {
return mapper.size();
}

/**
* Remove any elements whose key's are *not* in the passed set
*/
synchronized public void erase(Set<Integer> pidSet) {
Set<Integer> currentKeySet = mapper.keySet();
currentKeySet.stream()
.filter(pid -> !pidSet.contains(pid))
.collect(Collectors.toSet())
.forEach(pid -> mapper.remove(pid));
}

public static class AEISessionMeta {
final String sessionId;
final int realAgentId;

public AEISessionMeta(String sessionId, int realAgentId) {
this.sessionId = sessionId == null ? "" : sessionId;
this.realAgentId = realAgentId;
}

public boolean isValid() {
return !(sessionId == null || sessionId.isEmpty() || realAgentId == 0);
}
}
}
106 changes: 106 additions & 0 deletions agent-core/src/main/java/com/newrelic/agent/android/aei/AEITrace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.newrelic.agent.android.aei;

import com.newrelic.agent.android.logging.AgentLogManager;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* Model for the collected AEI trace data.
*
* The AEI trace filters and reduces the system trace returned
* by ApplicationExitInfo.getTraceInputStream() to just the data expected at ingest.
*/
public class AEITrace {
final static Pattern TRACE_HEADER_REGEXP = Pattern.compile(".*----- pid (?<pid>.\\d+) at (?<timeCreated>\\d{4}-\\d{2}-\\d{2}[T ]{0,}[0-9:.-]+) -----(?<body>.*$)");
final static Pattern TRACE_THREADS_REGEXP = Pattern.compile(".*DALVIK THREADS \\((?<threadCnt>\\d+)\\):\\s(.*)----- end (\\d+) -----", Pattern.MULTILINE);
final static Pattern TRACE_THREAD_ID_REGEXP = Pattern.compile("^\"(?<threadName>.*)\" (.*)prio=(\\d+).*$");

final ArrayList<String> threads;
String pid;
String createTime;

public AEITrace() {
threads = new ArrayList<String>();
}

public AEITrace(File filePath) {
this();
}

public AEITrace(int pid, File artifact) {
this(artifact);
this.pid = String.valueOf(pid);
}

public AEITrace decomposeFromSystemTrace(String sysTrace) {

// replace newlines with tabs to parse the entire trace as a tab delimited string
sysTrace = sysTrace.strip().replace('\n', '\t');

// ----- pid 4473 at 2024-02-15 23:37:45.593138790-0800 -----
Matcher headerMatcher = TRACE_HEADER_REGEXP.matcher(sysTrace);
if (headerMatcher.matches()) {
if (null == pid || pid.isBlank()) {
pid = headerMatcher.group(1);
}
createTime = headerMatcher.group(2).strip();
} else {
AgentLogManager.getAgentLog().debug("The trace file does not contain the expected file header.");
}

// DALVIK THREADS (<nThreads>):\n<thread 0>\n<thread 1>...\n<thread n>\n----- end (<pid>) -----
Matcher threadsMatcher = TRACE_THREADS_REGEXP.matcher(sysTrace);
if (threadsMatcher.matches()) {
String threadData = threadsMatcher.group(2).strip();
parseThreadsData(threadData);
} else {
AgentLogManager.getAgentLog().error("The trace file does not contain the expected threads data.");
// TODO try to parse the file anyway?
// parseThreadsData(sysTrace);
}

return this;
}

private ArrayList<String> parseThreadsData(String threadData) {
if (!(threadData == null || threadData.isEmpty())) {
threads.addAll(List.of(threadData.split("\t\t")));
threads.removeIf(s -> !TRACE_THREAD_ID_REGEXP.matcher(s).matches());
threads.replaceAll(s -> {
String[] frames = s.split("\t");
return Arrays.stream(frames)
.filter(s1 -> !s1.trim().matches("[(|-].*"))
.collect(Collectors.joining("\n"));
});
}

return threads;
}

public ArrayList<String> getThreads() {
return threads;
}

public String getPid() {
return pid;
}

public String getCreateTime() {
return createTime;
}

@Override
public String toString() {
// transform trace data per DEM spec
String flattenedThreads = threads.stream()
.collect(Collectors.joining("\n\n"));

return flattenedThreads;
}
}
Loading
Loading