Created
January 27, 2020 13:05
-
-
Save bennadel/ccf1f6c1670080449f36be9d752c5c29 to your computer and use it in GitHub Desktop.
Wrapping The FusionReactor API (FRAPI) For Safe Consumption In Lucee CFML 5.2.9.40
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
component | |
output = false | |
hint = "I help interoperate with the Java Agent that is instrumenting the ColdFusion application (which is provided by FusionReactor)." | |
{ | |
// I initialize the java agent helper. | |
public any function init() { | |
// The FusionReactor Agent is not available in all contexts. As such, we have to | |
// be careful about trying to load the Java Class; and then, be cautious of its | |
// existence when we try to consume it. The TYPE OF THIS VARIABLE will be used | |
// when determining whether or not the FusionReactor API should be consumed. This | |
// approach allows us to use the same code in the calling context without having | |
// to worry if the FusionReactor agent is installed. | |
try { | |
// NOTE: The FRAPI was on Version 8.2.3 at the time of this writing. | |
variables.FRAPIClass = createObject( "java", "com.intergral.fusionreactor.api.FRAPI" ); | |
} catch ( any error ) { | |
variables.FRAPIClass = ""; | |
} | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
/** | |
* I post the given metric as an AGGREGATE NUMERIC LONG metric; and, optionally, | |
* stream the metric to the Cloud dashboard. | |
* | |
* NOTE: In the FusionReactor documentation, metrics are all named using slash- | |
* notation. As in, "/this/is/my/metric". I recommend you follow this same pattern | |
* for consistency. | |
* | |
* @name I am the name of the metric. | |
* @value I am the LONG value of the metric. | |
* @output false | |
*/ | |
public void function metricAdd( | |
required string name, | |
required numeric value, | |
boolean enableCloudMetric = true | |
) { | |
if ( shouldUseFusionReactorApi() ) { | |
FRAPIClass.getInstance().postNumericAggregateMetric( | |
javaCast( "string", name ), | |
javaCast( "long", value ) | |
); | |
if ( enableCloudMetric ) { | |
FRAPIClass.getInstance().enableCloudMetric( javaCast( "string", name ) ); | |
} | |
} | |
} | |
/** | |
* I post the given metric as an AGGREGATE NUMERIC FLOAT metric; and, optionally, | |
* stream the metric to the Cloud dashboard. | |
* | |
* @name I am the name of the metric. | |
* @value I am the FLOAT value of the metric. | |
* @output false | |
*/ | |
public void function metricAddFloat( | |
required string name, | |
required numeric value, | |
boolean enableCloudMetric = true | |
) { | |
if ( shouldUseFusionReactorApi() ) { | |
FRAPIClass.getInstance().postNumericAggregateMetric( | |
javaCast( "string", name ), | |
javaCast( "float", value ) | |
); | |
if ( enableCloudMetric ) { | |
FRAPIClass.getInstance().enableCloudMetric( javaCast( "string", name ) ); | |
} | |
} | |
} | |
/** | |
* I associate the given property with the currently active transaction. | |
* | |
* @name I am the name of the custom property. | |
* @value I am the value of the custom property. | |
* @output false | |
*/ | |
public void function propertyAdd( | |
required string name, | |
required string value | |
) { | |
if ( shouldUseFusionReactorApi() ) { | |
FRAPIClass.getInstance().getActiveTransaction().setProperty( | |
javaCast( "string", name ), | |
javaCast( "string", value ) | |
); | |
} | |
} | |
/** | |
* I associate the given set of properties with the currently active transaction. | |
* | |
* NOTE: The values are expected to be Strings. | |
* | |
* @payload I am the set of key-value pairs to record as properties. | |
* @output false | |
*/ | |
public void function propertyAddAll( required struct payload ) { | |
for ( var key in payload ) { | |
propertyAdd( key, payload[ key ] ); | |
} | |
} | |
/** | |
* I end the segment and associate the resultant sub-transaction with the current | |
* parent transaction. | |
* | |
* @segment I am the OPAQUE TOKEN of the segment being ended and timed. | |
* @output false | |
*/ | |
public void function segmentEnd( required any segment ) { | |
if ( shouldUseFusionReactorApi() ) { | |
// In the case where the segment is not available (because the FusionReactor | |
// agent has not been installed), it will be represented as an empty string. | |
// In such cases, just ignore the request. | |
if ( isSimpleValue( segment ) ) { | |
return; | |
} | |
segment.close(); | |
} | |
} | |
/** | |
* I start and return a new Segment to be associated with the current request | |
* transaction. The returned Segment should be considered an OPAQUE TOKEN and should | |
* not be consumed directly. Instead, it should be passed to the .segmentEnd() method. | |
* Segments will show up in the Transaction Breakdown table, as well as in the | |
* "Relations" tab in the Standalone dashboard and the "Traces" tab in the Cloud | |
* dashboard. | |
* | |
* @name I am the name of the segment being started. | |
* @output false | |
*/ | |
public any function segmentStart( required string name ) { | |
if ( shouldUseFusionReactorApi() ) { | |
return( FRAPIClass.getInstance().createTrackedTransaction( javaCast( "string", name ) ) ); | |
} | |
// If the FusionReactor API feature is not enabled, we still need to return | |
// something as the OPAQUE SEGMENT TOKEN so that the calling logic can be handled | |
// uniformly within the application code. | |
return( "" ); | |
} | |
/** | |
* I wrap a new Segment with the given name around the execution of the given | |
* callback. Segments will show up in the Transaction Breakdown table, as well as in | |
* the "Relations" tab in the Standalone dashboard and the "Traces" tab in the Cloud | |
* dashboard. I pass-through the return value of the callback invocation. | |
* | |
* @name I am the name of the segment being started. | |
* @callback I am the callback being executed and timed. | |
* @output false | |
*/ | |
public any function segmentWrap( | |
required string name, | |
required function callback | |
) { | |
var segmentToken = segmentStart( name ); | |
try { | |
return( callback() ); | |
} finally { | |
segmentEnd( segmentToken ); | |
} | |
} | |
/** | |
* I set the name of the request's MASTER transaction (which is used to separate | |
* requests within the FusionReactor dashboard). | |
* | |
* CAUTION: Transaction names should container alpha-numeric characters. Including | |
* slashes or dots within the name appears to create some unexpected behaviors in the | |
* Standalone dashboard. | |
* | |
* @name I am the name of the transaction. It will be used as-is. | |
* @output false | |
*/ | |
public void function transactionSetName( required string name ) { | |
if ( shouldUseFusionReactorApi() ) { | |
FRAPIClass.getInstance().setTransactionName( javaCast( "string", name ) ); | |
} | |
} | |
/** | |
* I record the given value along with a time-stamp under the Traces tab of the master | |
* transaction. This information appears to only be available in the Standalone | |
* dashboard. | |
* | |
* NOTE: Struct, Array, and Simple values have been tested to work. | |
* | |
* @value I am the value being recorded. | |
* @output false | |
*/ | |
public void function traceAdd( required any value ) { | |
if ( shouldUseFusionReactorApi() ) { | |
FRAPIClass.getInstance().trace( value ); | |
} | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
/** | |
* I check to see if this machine should consume the FusionReactor static API as part | |
* of the Java Agent Helper class (this is to allow the methods to exist in the | |
* calling context without a lot of conditional consumption logic). | |
* | |
* @output false | |
*/ | |
private boolean function shouldUseFusionReactorApi() { | |
// If we were UNABLE TO LOAD THE FRAPI CLASS, there's no API to consume. | |
if ( isSimpleValue( FRAPIClass ) ) { | |
return( false ); | |
} | |
return( true ); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfscript> | |
// The JavaAgentHelper is a SIMPLIFIED wrapper around the FRAPI class that can safely | |
// be consumed even if the FRAPI class isn't installed. This allows the same code | |
// to run "consistently" across environments, even when the FusionReactor Java Agent | |
// isn't installed. | |
javaAgentHelper = new JavaAgentHelper(); | |
javaAgentHelper.transactionSetName( "Testing-Wrapper" ); | |
javaAgentHelper.propertyAddAll({ | |
userID: 4, | |
teamID: 99 | |
}); | |
javaAgentHelper.traceAdd( "Testing the wrapper." ); | |
// ------------------------------------------------------------------------------- // | |
// ------------------------------------------------------------------------------- // | |
startedAt = getTickCount(); | |
// For this test, we're going to mock the usage of a feature flag. | |
shouldUseExperiment = randRange( 0, 1 ); | |
// Experimental case. | |
if ( shouldUseExperiment ) { | |
// Use the callback approach to segment this code. Under the hood, this is | |
// creating a sub-transaction that can be seen under the Relations / Traces tabs | |
// within the various FusionReactor dashboards. | |
javaAgentHelper.segmentWrap( | |
"MyExperimentalSubTransaction", | |
() => { | |
sleep( randRange( 100, 500 ) ); | |
} | |
); | |
// Record an Aggregate Numeric Long metric for this experimental case. | |
javaAgentHelper.metricAdd( "/wrapper/experimental/duration", ( getTickCount() - startedAt ) ); | |
// Base case. | |
} else { | |
// Use the token approach to segment this code. Under the hood, this is creating | |
// a sub-transaction that can be seen under the Relations / Traces tabs within | |
// the various FusionReactor dashboards. | |
token = javaAgentHelper.segmentStart( "MyBaseSubTransaction" ); | |
try { | |
sleep( randRange( 500, 1500 ) ); | |
} finally { | |
javaAgentHelper.segmentEnd( token ); | |
} | |
// Record an Aggregate Numeric Long metric for this base case. | |
javaAgentHelper.metricAdd( "/wrapper/base/duration", ( getTickCount() - startedAt ) ); | |
} | |
</cfscript> | |
<!--- ------------------------------------------------------------------------------ ---> | |
<!--- ------------------------------------------------------------------------------ ---> | |
<script> | |
// Simulate regular throughput / traffic to this endpoint by refreshing. | |
setTimeout( | |
function() { | |
window.location.reload(); | |
}, | |
1000 | |
); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment