Skip to content

Commit 52111fa

Browse files
committed
1 parent 584991f commit 52111fa

File tree

2 files changed

+347
-1
lines changed

2 files changed

+347
-1
lines changed
+346
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
package org.jgroups.util;
2+
3+
import org.jgroups.annotations.Component;
4+
import org.jgroups.annotations.Property;
5+
import org.jgroups.stack.Protocol;
6+
7+
import java.io.*;
8+
import java.lang.reflect.Field;
9+
import java.lang.reflect.Method;
10+
import java.lang.reflect.Modifier;
11+
import java.util.*;
12+
import java.util.concurrent.ConcurrentSkipListMap;
13+
import java.util.concurrent.ConcurrentSkipListSet;
14+
import java.util.stream.Collectors;
15+
16+
/**
17+
* Tools to (1) dump all protocols and the names of their attributes ({@link org.jgroups.annotations.ManagedAttribute})
18+
* and properties ({@link Property}) to file, (2) read from that file ('old) and compare whether old is a proper
19+
* subset of new, ie. if all protocols and attributes/properties still have the same names in new.<br/>
20+
* To be run before releasing a new version (mainly minor and micro).
21+
* @author Bela Ban
22+
* @since 5.4.4
23+
*/
24+
public class CompareMetrics {
25+
protected static final String ROOT_PACKAGE="org.jgroups";
26+
protected static final String[] PACKAGES={
27+
"org.jgroups.protocols",
28+
"org.jgroups.protocols.pbcast",
29+
"org.jgroups.protocols.dns",
30+
"org.jgroups.protocols.relay"
31+
};
32+
33+
public static void main(String[] args) throws IOException, ClassNotFoundException {
34+
String from_file=null, to_file=null;
35+
for(int i=0; i < args.length; i++) {
36+
if("-from".equals(args[i])) {
37+
from_file=args[++i];
38+
continue;
39+
}
40+
if("-to".equals(args[i])) {
41+
to_file=args[++i];
42+
continue;
43+
}
44+
System.out.printf("%s (-from <read from file> | -to <dump to file>)\n", CompareMetrics.class.getSimpleName());
45+
return;
46+
}
47+
48+
// Sanity check
49+
if((from_file == null && to_file == null) || (from_file != null && to_file != null))
50+
throw new IllegalArgumentException("(only) one of '-from' or '-to' has to be defined");
51+
52+
Map<String,Collection<String>> old_metrics=null;
53+
if(from_file != null) {
54+
old_metrics=readOldMetrics(from_file);
55+
long total_attrs=old_metrics.values().stream().mapToLong(Collection::size).sum();
56+
System.out.printf("-- read old metrics: %d protocols and %,d attributes\n", old_metrics.size(), total_attrs);
57+
}
58+
59+
Map<String,Collection<String>> new_metrics=readCurrentMetrics();
60+
// System.out.printf("current metrics:\n%s\n", print(new_metrics));
61+
long total_attrs=new_metrics.values().stream().mapToLong(Collection::size).sum();
62+
System.out.printf("-- read current metrics: %d protocols and %,d attributes\n", new_metrics.size(), total_attrs);
63+
if(to_file != null) {
64+
// read the current metrics, dump to file and exit
65+
writeMetricsToFile(new_metrics, to_file);
66+
return;
67+
}
68+
69+
// from_file was used to read the old metrics; compare with the current and then exit
70+
compareMetrics(new_metrics, old_metrics);
71+
72+
old_metrics.put("UDP", List.of("number_of_messages"));
73+
new_metrics.put("UDP", List.of("num_msgs"));
74+
75+
if(old_metrics.isEmpty() && new_metrics.isEmpty()) {
76+
System.out.println("\n** Success: both old and new metrics are the same");
77+
return;
78+
}
79+
if(!old_metrics.isEmpty()) {
80+
System.out.printf("\n** Failure: the following protocols/attributes are only found in old " +
81+
"metrics, but not in new:\n%s\n", print(old_metrics));
82+
}
83+
if(!new_metrics.isEmpty()) {
84+
System.out.printf("\n** The following protocols/attributes are only found in new, but not in old " +
85+
"(this may not be an error, e.g. when new protocols or attributes have been added):\n" +
86+
"%s\n", print(new_metrics));
87+
}
88+
}
89+
90+
protected static Map<String,Collection<String>> readOldMetrics(String from_file) throws IOException {
91+
Map<String,Collection<String>> map=new ConcurrentSkipListMap<>();
92+
try(InputStream in=new FileInputStream(from_file)) {
93+
int i=1;
94+
for(;;) {
95+
String line=Util.readLine(in);
96+
if(line == null)
97+
break;
98+
int index=line.indexOf(":");
99+
String protocol=line.substring(0, index).trim();
100+
index=line.indexOf("[");
101+
int end=line.indexOf("]");
102+
String attributes=line.substring(index + 1, end);
103+
StringTokenizer tok=new StringTokenizer(attributes, ",");
104+
Collection<String> attrs=new ConcurrentSkipListSet<>();
105+
while(tok.hasMoreTokens()) {
106+
String attr=tok.nextToken().trim();
107+
attrs.add(attr);
108+
}
109+
map.put(protocol, attrs);
110+
}
111+
}
112+
return map;
113+
}
114+
115+
protected static Map<String,Collection<String>> readCurrentMetrics() throws IOException, ClassNotFoundException {
116+
SortedMap<String,Collection<String>> map=new ConcurrentSkipListMap<>();
117+
Set<Class<?>> classes=getProtocols();
118+
for(Class<?> cl: classes) {
119+
Collection<String> attrs=getAttributes(cl, null);
120+
if(!attrs.isEmpty())
121+
map.put(cl.getSimpleName(), attrs);
122+
}
123+
return map;
124+
}
125+
126+
protected static Set<Class<?>> getProtocols() throws IOException, ClassNotFoundException {
127+
ClassLoader cl=Thread.currentThread().getContextClassLoader();
128+
Set<Class<?>> s=new HashSet<>();
129+
for(String p: PACKAGES) {
130+
Set<Class<?>> tmp=Util.findClassesAssignableFrom(p, Protocol.class, cl);
131+
s.addAll(tmp);
132+
}
133+
return s;
134+
}
135+
136+
protected static void writeMetricsToFile(Map<String,Collection<String>> metrics, String to_file) throws IOException {
137+
try(OutputStream out=new FileOutputStream(to_file)) {
138+
for(Map.Entry<String,Collection<String>> e: metrics.entrySet()) {
139+
String s=String.format("%s: %s\n", e.getKey(), e.getValue());
140+
out.write(s.getBytes());
141+
}
142+
}
143+
}
144+
145+
/**
146+
* Compares the new to the old metrics by removing 'old' from 'new' (if present in both 'new' and 'old').<br/>
147+
* If protocols/attributes remain, then either new protocols or attributes were added in new, or attributes /
148+
* protocols changed. E.g. an attribute changes from "number_of_messages" -> "num_msgs", then "number_of_attributes"
149+
* will remain in 'old' and "num_msgs" in 'new'.<br/>
150+
* The goal is that all attributes of 'old' also need to be in 'new', or else we have an incompatible change in that
151+
* an attribute was renamed or removed. If an attribute or protocol is only in 'new', that's acceptable and means
152+
* that it was added in 'new.
153+
*/
154+
protected static void compareMetrics(Map<String,Collection<String>> new_metrics, Map<String,Collection<String>> old_metrics) {
155+
for(Iterator<Map.Entry<String,Collection<String>>> it=old_metrics.entrySet().iterator(); it.hasNext();) {
156+
Map.Entry<String,Collection<String>> old_entry=it.next();
157+
String key=old_entry.getKey();
158+
Collection<String> old_attrs=old_entry.getValue();
159+
Collection<String> new_attrs=new_metrics.get(key);
160+
boolean rc=compareAttributes(new_attrs, old_attrs);
161+
if(rc) {
162+
it.remove();
163+
new_metrics.remove(key);
164+
}
165+
}
166+
}
167+
168+
// Remove all attributes both in new and old
169+
// If both old and new are empty -> return true, else false
170+
protected static boolean compareAttributes(Collection<String> new_attrs, Collection<String> old_attrs) {
171+
for(Iterator<String> it=old_attrs.iterator(); it.hasNext();) {
172+
String old_attr=it.next();
173+
if(new_attrs.contains(old_attr)) {
174+
it.remove();
175+
new_attrs.remove(old_attr);
176+
}
177+
}
178+
return old_attrs.isEmpty() && new_attrs.isEmpty();
179+
}
180+
181+
protected static String print(Map<String,Collection<String>> map) {
182+
return map.entrySet().stream().map(e -> String.format("%s: %s", e.getKey(), e.getValue()))
183+
.collect(Collectors.joining("\n"));
184+
}
185+
186+
protected static Collection<String> getAttributes(Class<?> clazz, String prefix)
187+
throws IOException, ClassNotFoundException {
188+
Collection<String> ret=new ConcurrentSkipListSet<>();
189+
Field[] fields=clazz.getDeclaredFields();
190+
for(Field field: fields) {
191+
if(field.isAnnotationPresent(Property.class)) {
192+
String property=field.getName();
193+
Property annotation=field.getAnnotation(Property.class);
194+
String name=annotation.name();
195+
if(name != null && !name.trim().isEmpty())
196+
property=name.trim();
197+
if(prefix != null && !prefix.isEmpty())
198+
property=prefix + "." + property;
199+
ret.add(property);
200+
}
201+
202+
// is the field annotated with @Component?
203+
if(field.isAnnotationPresent(Component.class)) {
204+
Component ann=field.getAnnotation(Component.class);
205+
Class<?> type=field.getType();
206+
if(type.isInterface() || Modifier.isAbstract(type.getModifiers())) {
207+
Set<Class<?>> implementations=Util.findClassesAssignableFrom(ROOT_PACKAGE, type, Thread.currentThread().getContextClassLoader());
208+
for(Class<?> impl: implementations)
209+
ret.addAll(getAttributes(impl, ann.name()));
210+
}
211+
else
212+
ret.addAll(getAttributes(type, ann.name()));
213+
}
214+
}
215+
216+
Method[] methods=clazz.getDeclaredMethods();
217+
for(Method method: methods) {
218+
if(method.isAnnotationPresent(Property.class)) {
219+
Property annotation=method.getAnnotation(Property.class);
220+
String name=annotation.name();
221+
if(name.isEmpty())
222+
name=Util.methodNameToAttributeName(method.getName());
223+
if(prefix != null && !prefix.isEmpty())
224+
name=prefix + "." + name;
225+
ret.add(name);
226+
}
227+
}
228+
return ret;
229+
}
230+
231+
/**
232+
* Reads from the input stream and replaces occurrences of ${PROT} with p.get("PROT") and writes this to the
233+
* output stream. If no value is found, then the ${PROT} will simple be omitted from the output.
234+
* Escaped values of the form \${PROT} are not looked up and the value without the backslash will be written
235+
* to the output stream.
236+
*/
237+
protected static void replaceVariables(InputStream in, OutputStream out, Properties p) {
238+
boolean looping=true;
239+
while(looping) {
240+
try {
241+
int ch=in.read(), n1, n2;
242+
if(ch == -1)
243+
break;
244+
switch(ch) {
245+
case '\\':
246+
n1=in.read();
247+
n2=in.read();
248+
if(n1 == -1 || n2 == -1) {
249+
looping=false;
250+
if(n1 != -1)
251+
out.write(n1);
252+
break;
253+
}
254+
255+
if(n1 == '$' && n2 == '{') {
256+
String s=readUntilBracket(in);
257+
out.write(n1);
258+
out.write(n2);
259+
out.write(s.getBytes());
260+
out.write('}');
261+
}
262+
else {
263+
out.write(ch);
264+
out.write(n1);
265+
out.write(n2);
266+
}
267+
break;
268+
case '$':
269+
n1=in.read();
270+
if(n1 == -1) {
271+
out.write(ch);
272+
looping=false;
273+
}
274+
else {
275+
if(n1 == '{') {
276+
String s=readUntilBracket(in);
277+
writeVarToStream(s, p, out);
278+
}
279+
else {
280+
out.write(ch);
281+
out.write(n1);
282+
}
283+
}
284+
break;
285+
default:
286+
out.write(ch);
287+
}
288+
}
289+
catch(IOException e) {
290+
break;
291+
}
292+
}
293+
Util.close(in, out);
294+
}
295+
296+
297+
protected static void writeVarToStream(String var, Properties p, OutputStream out) throws IOException {
298+
String val=(String)p.get(var);
299+
if(val != null)
300+
out.write(val.getBytes());
301+
}
302+
303+
/** Reads until the next bracket '}' and returns the string excluding the bracket, or throws an exception if
304+
* no bracket has been found */
305+
protected static String readUntilBracket(InputStream in) throws IOException {
306+
StringBuilder sb=new StringBuilder();
307+
while(true) {
308+
int ch=in.read();
309+
switch(ch) {
310+
case -1: throw new EOFException("no matching } found");
311+
case '}':
312+
return sb.toString();
313+
default:
314+
sb.append((char)ch);
315+
}
316+
}
317+
}
318+
319+
private static String fileToString(File f) throws Exception {
320+
StringWriter output = new StringWriter();
321+
try (FileReader input = new FileReader(f)) {
322+
char[] buffer = new char[8 * 1024];
323+
int n;
324+
while (-1 != (n = input.read(buffer))) {
325+
output.write(buffer, 0, n);
326+
}
327+
}
328+
return output.toString();
329+
}
330+
331+
public static int copy(Reader input, Writer output) throws IOException {
332+
char[] buffer = new char[8 * 1024];
333+
int count = 0;
334+
int n = 0;
335+
try {
336+
while (-1 != (n = input.read(buffer))) {
337+
output.write(buffer, 0, n);
338+
count += n;
339+
}
340+
} finally {
341+
output.flush();
342+
output.close();
343+
}
344+
return count;
345+
}
346+
}

src/org/jgroups/util/Util.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -4141,7 +4141,7 @@ public static String readLine(InputStream in) throws IOException {
41414141
while(true) {
41424142
ch=in.read();
41434143
if(ch == -1)
4144-
return sb.toString();
4144+
return sb.length() == 0? null : sb.toString();
41454145
if(ch == '\r')
41464146
;
41474147
else {

0 commit comments

Comments
 (0)