Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created January 25, 2020 15:56
Show Gist options
  • Save bennadel/844fcf526294169f043f5f9f0a0b1e4c to your computer and use it in GitHub Desktop.
Save bennadel/844fcf526294169f043f5f9f0a0b1e4c to your computer and use it in GitHub Desktop.
Using The FusionReactor API (FRAPI) To Add Custom Instrumentation In Lucee CFML 5.2.9.40
<cfscript>
// Get the running FusionReactor API (FRAPI) instance from the FRAPI factory class.
// --
// Java Docs: https://www.fusion-reactor.com/frapi/7_0_0/com/intergral/fusionreactor/api/FRAPI.html
frapi = createObject( "java", "com.intergral.fusionreactor.api.FRAPI" )
.getInstance()
;
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// By default, FusionReactor will use the name of the application as defined in the
// Application.cfc ColdFusion framework component. However, we can set the name
// programmatically.
frapi.setTransactionApplicationName( "FRAPI-Testing" );
// By default, FusionReactor will calculate the transaction name based on the request
// context. It actually seems to "understand" the fact that we're using Framework One
// (FW/1) in production and uses the "action" value as the transaction name. That's
// the beauty of using an APM product that is embedded within the ColdFusion and CFML
// community. That said, we can set the transaction name programmatically.
// --
// See Framework Support: https://www.fusion-reactor.com/support/kb/frs-431/
frapi.setTransactionName( "GET/feature-flag-test" );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// SIMULATE FEATURE FLAG setting.
shouldUseExperiment = randRange( 0, 1 );
// We can use the trace() method to aggregate arbitrary time-stamped data along with
// the request. This data gets placed in a "Traces" tab in the Request detail.
// --
// CAUTION: At the time of this writing, this data is not accessible on the Cloud
// dashboard, only on the STANDALONE dashboard. And, this data SHOULD NOT be
// confused with the "Tracing" tab on the CLOUD dashboard, which is concerned with
// nested Transactions only.
frapi.trace( "Starting Experiment." );
// Try tracing a Struct.
frapi.trace({
usingExpeirment: yesNoFormat( shouldUseExperiment )
});
// Try tracing an Array.
frapi.trace([
"DATE",
dateFormat( now() ),
"TIME (EST)",
timeFormat( now() )
]);
// Let's imagine that this page is going to represent two different algorithms: the
// base one and an experimental one that we are testing with a feature flag. In
// order to see if our experiment is worthwhile, we're going to track the relative
// execution time of each approach.
startedAt = getTickCount();
// CASE: Experiment.
if ( shouldUseExperiment ) {
frapi.trace( "Starting experimental case." );
// We can associate arbitrary key-value pairs with the request. These will show
// up in the "Properties" tab of the request detail.
// --
// NOTE: At the time of this writing, these properties are not accessible on the
// Cloud dashboard, only on the Standalone dashboard.
frapi.getActiveTransaction()
.setProperty( "Features - Optimizations - Test", "True" )
;
// In addition to the timing metrics which we are going to record, we can also
// create segments, aka "sub transactions", that help us map the relative
// execution time for parts of the request.
// --
// NOTE: In the CLOUD dashboard, these show up in the "Traces" tab of a
// Transaction detail. In the STANDALONE dashboard, these who up in the
// "Relations" tab of a Transaction detail.
// --
// NOTE: In the CLOUD dashboard, these can also be found in the "Flavor" dropdown
// of the Transactions tab. In the STANDALONE dashboard, these can also be
// graphed under the Transactions section.
// --
// NOTE: When naming a transaction, I was running into issues in the STANDALONE
// dashboard if I used any kind of path-style notation (either "/" or "."): only
// the last segment of the "path" would show up, but would have no values.
try {
subtransaction = frapi.createTrackedTransaction( "HeavyProcessing-Exp-Start" );
// SIMULTATE algorithm time.
sleep( randRange( 25, 50 ) );
} finally {
subtransaction.close();
}
try {
subtransaction = frapi.createTrackedTransaction( "HeavyProcessing-Exp-End" );
// SIMULTATE algorithm time.
sleep( randRange( 25, 50 ) );
} finally {
subtransaction.close();
}
// CASE: Default.
} else {
frapi.trace( "Starting default case." );
// We can associate arbitrary key-value pairs with the request. These will show
// up in the "Properties" tab of the request detail.
// --
// NOTE: At the time of this writing, these properties are not accessible on the
// Cloud dashboard, only on the Standalone dashboard.
frapi.getActiveTransaction()
.setProperty( "Features - Optimizations - Test", "False" )
;
// In addition to the timing metrics which we are going to record, we can also
// create segments, aka "sub transactions", that help us map the relative
// execution time for parts of the request.
// --
// NOTE: In the CLOUD dashboard, these show up in the "Traces" tab of a
// Transaction detail. In the STANDALONE dashboard, these who up in the
// "Relations" tab of a Transaction detail.
// --
// NOTE: In the CLOUD dashboard, these can also be found in the "Flavor" dropdown
// of the Transactions tab. In the STANDALONE dashboard, these can also be
// graphed under the Transactions section.
// --
// NOTE: When naming a transaction, I was running into issues in the STANDALONE
// dashboard if I used any kind of path-style notation (either "/" or "."): only
// the last segment of the "path" would show up, but would have no values.
try {
subtransaction = frapi.createTrackedTransaction( "HeavyProcessing-Base-Start" );
// SIMULTATE algorithm time.
sleep( randRange( 1000, 2500 ) );
} finally {
subtransaction.close();
}
try {
subtransaction = frapi.createTrackedTransaction( "HeavyProcessing-Base-End" );
// SIMULTATE algorithm time.
sleep( randRange( 1000, 2500 ) );
} finally {
subtransaction.close();
}
} // END: Processing experiment.
duration = ( getTickCount() - startedAt );
// In addition to the Transaction-based recording we're doing above, we can also
// record custom metrics which we can then graph in the FusionReactor dashboard.
// --
// NOTE: FusionReactor's documentation seems to use a path-style notation when naming
// custom metrics. As such, I am just following the same pattern in the following
// metric examples.
// --
// NOTE: The metrics are always available in the STANDALONE version of the
// FusionReactor dashboard; but, to stream them to the CLOUD dashboard as well, the
// metric has to be explicitly enabled with .enableCloudMetric(), and only works for
// NUMERIC AGGREGATE metrics.
if ( shouldUseExperiment ) {
frapi.postNumericAggregateMetric( "/optimizations/exp/duration", duration );
frapi.enableCloudMetric( "/optimizations/exp/duration" );
} else {
frapi.enableCloudMetric( "/optimizations/base/duration" );
frapi.postNumericAggregateMetric( "/optimizations/base/duration", duration );
}
frapi.trace( "Ending Experiment." );
</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