Node performance measurement with a decorator

Darragh O'Riordan - Oct 6 '20 - - Dev Community

I needed to measure node method performance recently. I figured since this was measuring some wrapped code anyway it would be a great candidate for an es6 decorator.

I use Azure Application Insights to store the metric here. But you could log to any system or just log to console locally.

Adding application insights to your app

You need to install the library

yarn add applicationinsights
Enter fullscreen mode Exit fullscreen mode

Add the env var with your connection string. You get the app insights connection string from the overview page on App insights in Azure.

APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=a-guid-key;IngestionEndpoint=https:/in.applicationinsights.azure.com/
Enter fullscreen mode Exit fullscreen mode

Then import the library REALLY EARLY. You should import it as the first thing you import in the entire application. That’s usually the index.js for the app.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import AppInsights = require('applicationinsights')
Enter fullscreen mode Exit fullscreen mode

Then optionally configure some of the parameters for the default client. The default client is what we will use later to measure metrics.

// These are all the library settings
AppInsights.setup()
  .setAutoDependencyCorrelation(true)
  .setAutoCollectRequests(true)
  .setAutoCollectPerformance(true, true)
  .setAutoCollectExceptions(true)
  .setAutoCollectDependencies(true)
  .setAutoCollectConsole(true)
  .setUseDiskRetryCaching(true)
  .setSendLiveMetrics(false)
  .setDistributedTracingMode(AppInsights.DistributedTracingModes.AI_AND_W3C)
  .start()
// It's a good idea to name the cloud role. This helps later when looking at the metrics on Azure.
AppInsights.defaultClient.context.tags[
  AppInsights.defaultClient.context.keys.cloudRole
] = 'My awesome app'

// If you use any kind of versioning you can set this for application insights also. Let's just pull the version out of the package.json file
AppInsights.defaultClient.context.tags['ai.application.ver'] =
  process.env.npm_package_version || '99.99.99'
Enter fullscreen mode Exit fullscreen mode

Create the decorator

import appInsights = require('applicationinsights')

export type TimerOptions = {
  name: string
}

// start a decorator
export function PerformanceTimer(options: TimerOptions) {
  return (
    target: unknown,
    propertyKey: string,
    propertyDescriptor: PropertyDescriptor
  ): PropertyDescriptor => {
    // Get a ref to method we're wrapping
    const originalMethod = propertyDescriptor.value
    // Get the name the developer provided
    const timerName = options.name

    // eslint-disable-next-line unicorn/prevent-abbreviations
    propertyDescriptor.value = async function (...args: never[]) {
      // start a timer
      const t0 = process.hrtime.bigint()

      // call the method
      const result = await originalMethod.apply(this, args)

      // stop the timer
      const timerValue = (process.hrtime.bigint() - t0) / BigInt(1000000)

      // log the result to azure. You could just change this to console logging
      appInsights.defaultClient &&
        appInsights.defaultClient.trackMetric({
          name: timerName,
          value: Number(timerValue),
        })
      return result
    }
    return propertyDescriptor
  }
}
Enter fullscreen mode Exit fullscreen mode

How to use

We just call it as a decorator. No need to import and libraries to service classes or anything.

    @PerformanceTimer({ name: "Measure LongRunningMethod" })
    public async someLongRunningMethod(): Promise<string> {
      ...
    }
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player