-
Notifications
You must be signed in to change notification settings - Fork 333
Expand file tree
/
Copy pathAgentJarIndex.java
More file actions
212 lines (190 loc) · 7.32 KB
/
AgentJarIndex.java
File metadata and controls
212 lines (190 loc) · 7.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package datadog.trace.bootstrap;
import datadog.trace.util.ClassNameTrie;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Maintains an index to map class/resource names under nested jar prefixes (inst, metrics, etc.)
*/
public final class AgentJarIndex {
private static final Logger log = LoggerFactory.getLogger(AgentJarIndex.class);
private static final String AGENT_INDEX_FILE_NAME = "dd-java-agent.index";
private final String[] prefixes;
private final ClassNameTrie prefixTrie;
private AgentJarIndex(String[] prefixes, ClassNameTrie prefixTrie) {
this.prefixes = prefixes;
this.prefixTrie = prefixTrie;
}
/** Returns the resolved entry name in the jar for the given resource. */
public String resourceEntryName(String name) {
int prefixId = prefixTrie.apply(name);
if (prefixId == 0) {
return name;
} else if (prefixId > 0) {
return prefixes[prefixId - 1] + (name.endsWith(".class") ? name + "data" : name);
} else {
return null;
}
}
/** Returns the resolved entry name in the jar for the given class. */
public String classEntryName(String name) {
int prefixId = prefixTrie.apply(name);
if (prefixId == 0) {
return name.replace('.', '/') + ".class";
} else if (prefixId > 0) {
return prefixes[prefixId - 1] + name.replace('.', '/') + ".classdata";
} else {
return null;
}
}
/** For testing purposes only. */
public static AgentJarIndex emptyIndex() {
return new AgentJarIndex(new String[0], ClassNameTrie.Builder.EMPTY_TRIE);
}
public static AgentJarIndex readIndex(JarFile agentJar) {
try {
ZipEntry indexEntry = agentJar.getEntry(AGENT_INDEX_FILE_NAME);
try (DataInputStream in =
new DataInputStream(new BufferedInputStream(agentJar.getInputStream(indexEntry)))) {
int prefixCount = in.readInt();
String[] prefixes = new String[prefixCount];
for (int i = 0; i < prefixCount; i++) {
prefixes[i] = in.readUTF();
}
return new AgentJarIndex(prefixes, ClassNameTrie.readFrom(in));
}
} catch (Throwable e) {
log.error("Unable to read {}", AGENT_INDEX_FILE_NAME, e);
return null;
}
}
/**
* Generates an index from the contents of the 'build/resources' directory that makes up the agent
* jar.
*/
static class IndexGenerator extends SimpleFileVisitor<Path> {
private static final Set<String> ignoredFileNames =
new HashSet<>(Arrays.asList("MANIFEST.MF", "NOTICE", "LICENSE.renamed"));
private final Path resourcesDir;
private final List<String> prefixes = new ArrayList<>();
private final ClassNameTrie.Builder prefixTrie = new ClassNameTrie.Builder();
private Path prefixRoot;
private int prefixId;
private Map<Integer, String> prefixMappings = new HashMap<>();
IndexGenerator(Path resourcesDir) {
this.resourcesDir = resourcesDir;
prefixTrie.put("datadog.*", 0);
}
public void writeIndex(Path indexFile) throws IOException {
try (DataOutputStream out =
new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(indexFile)))) {
out.writeInt(prefixes.size());
for (String p : prefixes) {
out.writeUTF(p);
}
prefixTrie.writeTo(out);
}
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (dir.getParent().equals(resourcesDir)) {
prefixRoot = dir;
prefixes.add(dir.getFileName() + "/");
prefixId = prefixes.size();
prefixMappings.put(prefixId, dir.getFileName().toString());
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
if (dir.equals(prefixRoot)) {
prefixRoot = null;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (null != prefixRoot) {
String entryKey = computeEntryKey(prefixRoot.relativize(file));
if (null != entryKey) {
int existingPrefixId = prefixTrie.apply(entryKey);
// warn if two subsections contain content under the same package prefix
// because we're then unable to redirect requests to the right submodule
// (ignore the two 'datadog.compiler' packages which allow duplication)
if (existingPrefixId > 0 && prefixId != existingPrefixId) {
log.warn(
"Detected duplicate content '{}' under '{}', already seen in {}. Ensure your content is under a distinct directory.",
entryKey,
resourcesDir.relativize(file).getName(0), // prefix
prefixMappings.get(existingPrefixId) // previous prefix
);
}
prefixTrie.put(entryKey, prefixId);
if (entryKey.endsWith("*")) {
// optimization: wildcard will match everything under here so can skip
return FileVisitResult.SKIP_SIBLINGS;
}
}
}
return FileVisitResult.CONTINUE;
}
private static String computeEntryKey(Path path) {
if (ignoredFileNames.contains(path.getFileName().toString())) {
return null;
}
String entryKey = path.toString();
if (File.separatorChar != '/') {
entryKey = entryKey.replace(File.separatorChar, '/');
}
if (entryKey.startsWith("datadog/trace/instrumentation/")) {
return "datadog.trace.instrumentation.*";
}
// use number of elements in the path to decide how 'unique' this path is
int nameCount = path.getNameCount();
if (nameCount > 1) {
if (entryKey.startsWith("META-INF")) { // don't count META-INF as a unique element
nameCount--;
}
// paths with three or more elements, or nested paths containing '.classdata' files
// are considered unique enough that we can use the directory name as a wildcard key
if (nameCount > 2 || entryKey.endsWith(".classdata")) {
entryKey = entryKey.substring(0, entryKey.lastIndexOf('/') + 1) + "*";
}
}
return entryKey.replace('/', '.');
}
public static void main(String[] args) throws IOException {
if (args.length < 1) {
throw new IllegalArgumentException("Expected: resources-dir");
}
Path resourcesDir = Paths.get(args[0]).toAbsolutePath();
Path indexDir = resourcesDir;
if (args.length == 2) {
indexDir = Paths.get(args[1]).toAbsolutePath();
}
IndexGenerator indexGenerator = new IndexGenerator(resourcesDir);
Files.walkFileTree(resourcesDir, indexGenerator);
indexGenerator.writeIndex(indexDir.resolve(AGENT_INDEX_FILE_NAME));
}
}
}