2022.04.25[Mon]

コンポーネントの処理時間を計測するHOC(Higher-Order Component)を作る

  • パフォーマンス
  • React
  • NewRelic

目次

  • - 環境
  • - コード
  • - 解説
  • - New Relic APMで確認

New Relic APM はサーバーサイドの総合監視・計測ツールです。
その中の拡張用の機能、Node.js agent API を用いたコンポーネントの処理時間計測を紹介します。
似たような関数の計測はChrome DevToolsのPerformanceタブ等の開発ツールでも行えると思いますが、任意の範囲でセグメントを分けられる・自由な命名・収集データに乗ってくるので自動で見れるようになる、といった利点があります。

環境

Node.js : 14.18.3
APM agent : 8.5.0

コード

先にコードを示します。
Node.js agent API の startSegment メソッドを利用します。
(公式ドキュメント: https://docs.newrelic.com/jp/docs/apm/agents/nodejs-agent/api-guides/nodejs-agent-api/#startSegment )

import React from 'react';

const isServer = !(typeof window !== 'undefined');

// A. 中身のNew Relic関数
function nrTrace(
  segmentName: string,
  argf: any,
  component: PropertyDescriptor,
  args: any[]
) {
  if (isServer) {
    if (!newrelic) {
      return false;
    }
    return newrelic.startSegment(segmentName, true, () => {
      return argf.apply(component, args);
    });
  }

  // クライアント側では元の関数をそのまま返す
  return argf.apply(component, args);
}

// B. デコレータ
function trace(segmentName = 'Anonymous') {
  return (target: any, name: string, descriptor: PropertyDescriptor) => {
    // NR missing - Server
    if (isServer && newrelic && !newrelic.agent) {
      return descriptor;
    }
    // NR missing - Client
    if (!isServer && typeof newrelic === 'undefined') {
      return descriptor;
    }

    if (typeof descriptor.value === 'function') {
      const descriptorValue = descriptor.value;

      descriptor.value = function(...args: any[]) {
        try {
          return nrTrace(segmentName, descriptorValue, this, args);
        } catch (e) {
          console.log(`Error: ${e}`);
          throw e;
        }
      };
    }

    return descriptor;
  };
}

// C. 実際にコンポーネント側に付けるHOC
export function withTrace(WrappedComponent: React.FC<any>) {
  return class extends React.Component {

    @trace(WrappedComponent.displayName)
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}




上記コードをインポートした上で、コンポーネント側でこのように使います。observerなどが付く場合はそれも含めて囲います。

import React from 'react';
import { withTrace } from '~/helpers/nrTrace';

const SomeComponent: React.FC = () => {

// some statements 

return (
  <div>
   some elements
  </div>
  )
}

SomeComponent.displayName = 'SomeComponent';
export default withTrace(SomeComponent);

////
// observer等が付く場合
////
const wrapped = observer(SomeComponent);
wrapped.displayName = 'SomeComponent';
export default withTrace(wrapped);

解説

コード内 // A. 中身のNew Relic関数 のNew Relicの関数を適用するためのデコレータが // B. デコレータ です。しかし関数コンポーネントをデコレートするには一段上に差し込む場所が必要になるため、// C. 実際にコンポーネント側に付けるHOC のHOCで括ってその中で適用しています。ここでは、HOCは簡素にただラップするのみです。
startSegmentは、APM agent すなわちサーバー側の関数であり、クライアント側では未定義でエラーになります。そのため、// A. 中身のNew Relic関数 内ではisServerで判定が必要になります。

ちなみにクラスコンポーネントを使っている場合は、traceをexportしてそのまま使うことができます。

New Relic APMで確認

New Relic APM の左メニューのTransaction タブを開いた時の Breakdown table の中に Custom として出てきます。複数回呼ばれるコンポーネントの場合はAvg callsがその分増えます。

画像からわかる通り、より大きな処理(特に外部通信:External)と比べると全体への影響は微々たるものです。
単体で検査するというよりは、ある程度大きな粒度で複数付けると、大きさの比較や不要なレンダリングが無いかの確認などに利用することができます。

Share

Next.jsでビルド時に最適化されたXMLサイトマップ(sitemap.xml)を作成するLCPはどのエレメントが判定されているか確認しないといけない