33package com .microsoft .gctoolkit ;
44
55import com .microsoft .gctoolkit .aggregator .Aggregation ;
6+ import com .microsoft .gctoolkit .aggregator .Aggregator ;
7+ import com .microsoft .gctoolkit .aggregator .EventSource ;
68import com .microsoft .gctoolkit .io .DataSource ;
79import com .microsoft .gctoolkit .io .GCLogFile ;
810import com .microsoft .gctoolkit .io .RotatingGCLogFile ;
1416import com .microsoft .gctoolkit .message .JVMEventChannel ;
1517
1618import java .io .IOException ;
19+ import java .lang .reflect .Constructor ;
1720import java .lang .reflect .InvocationTargetException ;
21+ import java .lang .reflect .Parameter ;
1822import java .util .ArrayList ;
1923import java .util .Arrays ;
2024import java .util .HashSet ;
2125import java .util .List ;
2226import java .util .Optional ;
2327import java .util .ServiceConfigurationError ;
2428import java .util .ServiceLoader ;
29+ import java .util .Set ;
30+ import java .util .function .Supplier ;
2531import java .util .logging .Level ;
2632import java .util .logging .Logger ;
33+ import java .util .stream .Collector ;
2734import java .util .stream .Collectors ;
2835
2936import static java .lang .Class .forName ;
@@ -35,6 +42,38 @@ public class GCToolKit {
3542
3643 private static final Logger LOGGER = Logger .getLogger (GCToolKit .class .getName ());
3744
45+ private static final String GCTOOLKIT_DEBUG = System .getProperty ("gctoolkit.debug" );
46+ private static final boolean DEBUGGING = GCTOOLKIT_DEBUG != null ;
47+
48+ // returns true if gctoolkit.debug is set to "all" or contains "className", but does not contain "-className"
49+ private static boolean isDebugging (String className ) {
50+ return DEBUGGING
51+ && (GCTOOLKIT_DEBUG .isEmpty ()
52+ || ((GCTOOLKIT_DEBUG .contains ("all" ) || GCTOOLKIT_DEBUG .contains (className ))
53+ && !GCTOOLKIT_DEBUG .contains ("-" + className )));
54+ }
55+
56+ /**
57+ * Print a debug message to System.out if gctoolkit.debug is empty, is set to "all",
58+ * or contains "className" but does not contain "-className".
59+ * For example, to enable debug logging for all classes except UnifiedG1GCParser:
60+ * <code>-Dgctoolkit.debug=all,-com.microsoft.gctoolkit.parser.UnifiedG1GCParser</code>
61+ *
62+ * @param message Supplies the message to log. If null, nothing will be logged.
63+ */
64+ public static void LOG_DEBUG_MESSAGE (Supplier <String > message ) {
65+ if (DEBUGGING && message != null ) {
66+ StackTraceElement [] stackTrace = Thread .currentThread ().getStackTrace ();
67+ String methodName = stackTrace [2 ].getMethodName ();
68+ String className = stackTrace [2 ].getClassName ();
69+ String fileName = stackTrace [2 ].getFileName ();
70+ int lineNumber = stackTrace [2 ].getLineNumber ();
71+ if (isDebugging (className )) {
72+ System .out .println (String .format ("DEBUG: %s.%s(%s:%d): %s" , className , methodName , fileName , lineNumber , message .get ()));
73+ }
74+ }
75+ }
76+
3877 private final HashSet <DataSourceParser > registeredDataSourceParsers = new HashSet <>();
3978 private List <Aggregation > registeredAggregations ;
4079 private JVMEventChannel jvmEventChannel = null ;
@@ -71,10 +110,10 @@ public void loadAggregationsFromServiceLoader() {
71110 ServiceLoader .load (Aggregation .class )
72111 .stream ()
73112 .map (ServiceLoader .Provider ::get )
74- .forEach (registeredAggregations :: add );
75- //Useful for debugging
76- if ( Level . FINER . equals ( LOGGER . getLevel ()))
77- registeredAggregations . forEach ( a -> LOGGER . log ( Level . FINER , "Registered " + a . toString ()) );
113+ .forEach (aggregation -> {
114+ registeredAggregations . add ( aggregation );
115+ LOG_DEBUG_MESSAGE (() -> "ServiceLoader provided: " + aggregation . getClass (). getName ());
116+ } );
78117 }
79118
80119 /**
@@ -154,19 +193,41 @@ private void loadJVMEventChannel() {
154193 }
155194 }
156195
196+ /**
197+ * This method allows full control over which DataSourceParsers are used to parse the DataSource.
198+ * This method should be called before the {@link #analyze(DataSource)} method.
199+ * DataSourceParsers loaded by this method are used in place of those that are ordinarily loaded via
200+ * the service provider interface.
201+ * Use the {@link #addDataSourceParser(DataSourceParser)} method to load a DataSourceParser in addition
202+ * to those loaded by the service provider interface.
203+ * @param dataSourceParser An implementation of DataSourceParser that will be used to parse the DataSource.
204+ */
157205 public void loadDataSourceParser (DataSourceParser dataSourceParser ) {
158206 registeredDataSourceParsers .add (dataSourceParser );
159207 }
160208
161- private void loadDataSourceParsers (Diary diary ) {
209+ private List <DataSourceParser > additiveParsers = new ArrayList <>();
210+
211+ /**
212+ * Add a DataSourceParser to be used to parse a DataSource. The DataSourceParser will be used in addition
213+ * to those loaded by the service provider interface. This method should be called before the
214+ * {@link #analyze(DataSource)} method.
215+ * @param dataSourceParser An implementation of DataSourceParser that will be used to parse the DataSource.
216+ */
217+ public void addDataSourceParser (DataSourceParser dataSourceParser ) {
218+ additiveParsers .add (dataSourceParser );
219+ }
220+
221+ private Set <EventSource > loadDataSourceParsers (Diary diary ) {
222+
162223 loadDataSourceChannel ();
163224 loadJVMEventChannel ();
164225 List <DataSourceParser > dataSourceParsers ;
165226 if (registeredDataSourceParsers .isEmpty ()) {
166227 dataSourceParsers = ServiceLoader .load (DataSourceParser .class )
167228 .stream ()
168229 .map (ServiceLoader .Provider ::get )
169- .filter (consumer -> consumer .accepts (diary ))
230+ .filter (dataSourceParser -> dataSourceParser .accepts (diary ))
170231 .collect (Collectors .toList ());
171232 } else {
172233 dataSourceParsers = new ArrayList <>();
@@ -202,18 +263,28 @@ private void loadDataSourceParsers(Diary diary) {
202263 })
203264 .filter (Optional ::isPresent )
204265 .map (optional -> (DataSourceParser ) optional .get ())
205- .filter (consumer -> consumer .accepts (diary ))
266+ .filter (dataSourceParser -> dataSourceParser .accepts (diary ))
206267 .collect (Collectors .toList ());
207- if (dataSourceParsers .isEmpty ()) {
208- throw new ServiceConfigurationError ("Unable to find a suitable provider to create a DataSourceParser" );
209- }
268+
269+ }
270+
271+ // add in any additional parsers not provided by the module SPI.
272+ dataSourceParsers .addAll (additiveParsers );
273+
274+ if (dataSourceParsers .isEmpty ()) {
275+ throw new ServiceConfigurationError ("Unable to find a suitable provider to create a DataSourceParser" );
210276 }
211277
212278 for (DataSourceParser dataSourceParser : dataSourceParsers ) {
279+ LOG_DEBUG_MESSAGE (() -> "Registering " + dataSourceParser .getClass ().getName () + " with " + dataSourceChannel .getClass ().getName ());
213280 dataSourceParser .diary (diary );
214281 dataSourceChannel .registerListener (dataSourceParser );
215282 dataSourceParser .publishTo (jvmEventChannel );
216283 }
284+
285+ return dataSourceParsers .stream ()
286+ .map (DataSourceParser ::eventsProduced )
287+ .collect (HashSet ::new , Set ::addAll , Set ::addAll );
217288 }
218289
219290 /**
@@ -230,14 +301,57 @@ private void loadDataSourceParsers(Diary diary) {
230301 */
231302 public JavaVirtualMachine analyze (DataSource <?> dataSource ) throws IOException {
232303 GCLogFile logFile = (GCLogFile )dataSource ;
233- loadDataSourceParsers (logFile .diary ());
304+ Set < EventSource > events = loadDataSourceParsers (logFile .diary ());
234305 JavaVirtualMachine javaVirtualMachine = loadJavaVirtualMachine (logFile );
235306 try {
236- javaVirtualMachine .analyze (registeredAggregations , jvmEventChannel , dataSourceChannel );
307+ List <Aggregator <? extends Aggregation >> filteredAggregators = filterAggregations (events );
308+ javaVirtualMachine .analyze (filteredAggregators , jvmEventChannel , dataSourceChannel );
237309 } catch (Throwable t ) {
238310 LOGGER .log (Level .SEVERE , "Internal Error: Cannot invoke analyze method" , t );
239311 }
240312 return javaVirtualMachine ;
241313 }
242314
315+ private List <Aggregator <? extends Aggregation >> filterAggregations (Set <EventSource > events ) {
316+ List <Aggregator <? extends Aggregation >> aggregators = new ArrayList <>();
317+ for (Aggregation aggregation : registeredAggregations ) {
318+ LOG_DEBUG_MESSAGE (() -> "Evaluating: " + aggregation .getClass ().getName ());
319+ Constructor <? extends Aggregator <?>> constructor = constructor (aggregation );
320+ if (constructor == null ) {
321+ LOGGER .log (Level .WARNING , "Cannot find one of: default constructor or @Collates annotation for " + aggregation .getClass ().getName ());
322+ continue ;
323+ }
324+
325+ Aggregator <? extends Aggregation > aggregator = null ;
326+ try {
327+ aggregator = constructor .newInstance (aggregation );
328+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e ) {
329+ LOGGER .log (Level .SEVERE , e .getMessage (), e );
330+ continue ;
331+ }
332+ if (events .stream ().anyMatch (aggregator ::aggregates )) {
333+ LOG_DEBUG_MESSAGE (() -> "Including : " + aggregation .getClass ().getName ());
334+ aggregators .add (aggregator );
335+ } else {
336+ LOG_DEBUG_MESSAGE (() -> "Excluding : " + aggregation .getClass ().getName ());
337+ }
338+ }
339+ return aggregators ;
340+
341+ }
342+
343+ @ SuppressWarnings ("unchecked" )
344+ private Constructor <? extends Aggregator <?>> constructor (Aggregation aggregation ) {
345+ Class <? extends Aggregator <?>> targetClazz = aggregation .collates ();
346+ if ( targetClazz != null ) {
347+ Constructor <?>[] constructors = targetClazz .getConstructors ();
348+ for (Constructor <?> constructor : constructors ) {
349+ Parameter [] parameters = constructor .getParameters ();
350+ if (parameters .length == 1 && Aggregation .class .isAssignableFrom (parameters [0 ].getType ()))
351+ return (Constructor <? extends Aggregator <?>>) constructor ;
352+ }
353+ }
354+ return null ;
355+ }
356+
243357}
0 commit comments