{"version":3,"file":"LoafInstrumentation.cjs","names":["EmbraceInstrumentationBase","isEntryTypeSupported","createPerformanceObserver","MAX_SCRIPT_URL_LENGTH","KEY_EMB_TYPE","generateWebVitalID","ATTR_TBD_LOAF_TOTAL_DURATION","ATTR_TBD_LOAF_WORK_DURATION","ATTR_TBD_LOAF_STYLE_AND_LAYOUT_DURATION","ATTR_TBD_LOAF_COUNT","ATTR_TBD_LOAF_LONGEST_DURATION","ATTR_TBD_LOAF_LONGEST_DURATION_EXCLUDING_FIRST","LOAF_EVENT_NAME","SeverityNumber","LOAF_SCRIPTS_EVENT_NAME"],"sources":["../../../../src/instrumentations/loaf/LoafInstrumentation/LoafInstrumentation.ts"],"sourcesContent":["/* eslint-disable baseline-js/use-baseline */\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport type { Metric } from 'web-vitals';\nimport type { SpanSessionManager } from '../../../api-sessions/index.ts';\nimport { EMB_TYPES, KEY_EMB_TYPE } from '../../../constants/index.ts';\nimport { generateWebVitalID } from '../../../utils/generateWebVitalID.ts';\nimport {\n  createPerformanceObserver,\n  isEntryTypeSupported,\n} from '../../../utils/index.ts';\nimport { EmbraceInstrumentationBase } from '../../EmbraceInstrumentationBase/index.ts';\nimport {\n  ATTR_TBD_LOAF_COUNT,\n  ATTR_TBD_LOAF_LONGEST_DURATION,\n  ATTR_TBD_LOAF_LONGEST_DURATION_EXCLUDING_FIRST,\n  ATTR_TBD_LOAF_STYLE_AND_LAYOUT_DURATION,\n  ATTR_TBD_LOAF_TOTAL_DURATION,\n  ATTR_TBD_LOAF_WORK_DURATION,\n  BLOCKING_DURATION_GOOD_THRESHOLD,\n  BLOCKING_DURATION_POOR_THRESHOLD,\n  LOAF_EVENT_NAME,\n  LOAF_SCRIPTS_EVENT_NAME,\n  MAX_SCRIPT_ENTRIES,\n  MAX_SCRIPT_URL_LENGTH,\n} from './constants.ts';\nimport type { LoafInstrumentationArgs } from './types.ts';\n\ntype ScriptSummaryValue = {\n  totalDuration: number;\n  styleAndLayoutDuration: number;\n  count: number;\n};\n\ntype ScriptSummaries = Map<string, ScriptSummaryValue>;\n\nexport class LoafInstrumentation extends EmbraceInstrumentationBase {\n  private _observer: PerformanceObserver | null = null;\n  private _isFirstEntry = true;\n  private _removeSessionEndListener: (() => void) | null = null;\n  private _isEnabled = false;\n\n  private _totalDuration = 0;\n  private _workDuration = 0;\n  private _styleLayoutDuration = 0;\n  private _count = 0;\n  private _longestDuration = 0;\n  private _longestDurationExcludingFirst = 0;\n  private _totalBlockingDuration = 0;\n  private _scriptSummaries: ScriptSummaries = new Map();\n\n  public constructor({ diag, perf }: LoafInstrumentationArgs = {}) {\n    super({\n      instrumentationName: 'LoafInstrumentation',\n      instrumentationVersion: '1.0.0',\n      diag,\n      perf,\n      config: {},\n    });\n\n    if (this._config.enabled) {\n      this.enable();\n    }\n  }\n\n  public enable(): void {\n    if (this._isEnabled) {\n      return;\n    }\n\n    if (!isEntryTypeSupported('long-animation-frame')) {\n      this._diag.debug('long-animation-frame not supported, skipping');\n      return;\n    }\n\n    this._isEnabled = true;\n\n    if (this._observer) {\n      this._observer.disconnect();\n    }\n\n    this._observer =\n      createPerformanceObserver<PerformanceLongAnimationFrameTiming>(\n        'long-animation-frame',\n        (entry) => this._processEntry(entry),\n        { diag: this._diag },\n      );\n\n    if (!this._observer) {\n      this._isEnabled = false;\n      this._diag.error('failed to enable');\n      return;\n    }\n\n    this._registerSessionEndListener();\n  }\n\n  private _registerSessionEndListener(): void {\n    if (!this._isEnabled) {\n      return;\n    }\n\n    if (this._removeSessionEndListener) {\n      this._removeSessionEndListener();\n    }\n\n    this._removeSessionEndListener =\n      this.sessionManager.addSessionEndedListener(() => {\n        try {\n          this._flushReport();\n        } catch (e) {\n          this._diag.error('error flushing report', e);\n        }\n      });\n  }\n\n  public override setSessionManager(sessionManager: SpanSessionManager): void {\n    super.setSessionManager(sessionManager);\n    this._registerSessionEndListener();\n  }\n\n  public disable(): void {\n    this._isEnabled = false;\n    this._resetAccumulators();\n\n    if (this._observer) {\n      this._observer.disconnect();\n      this._observer = null;\n    }\n\n    if (this._removeSessionEndListener) {\n      this._removeSessionEndListener();\n      this._removeSessionEndListener = null;\n    }\n  }\n\n  private _processEntry(entry: PerformanceLongAnimationFrameTiming): void {\n    if (!this._isEnabled) {\n      return;\n    }\n\n    this._count++;\n    this._totalDuration += entry.duration;\n    this._workDuration += entry.renderStart\n      ? entry.renderStart - entry.startTime\n      : entry.duration;\n\n    if (entry.styleAndLayoutStart) {\n      this._styleLayoutDuration += Math.max(\n        0,\n        entry.startTime + entry.duration - entry.styleAndLayoutStart,\n      );\n    }\n\n    this._longestDuration = Math.max(this._longestDuration, entry.duration);\n\n    if (this._isFirstEntry && this._count === 1) {\n      this._isFirstEntry = false;\n    } else {\n      this._longestDurationExcludingFirst = Math.max(\n        this._longestDurationExcludingFirst,\n        entry.duration,\n      );\n      if (entry.firstUIEventTimestamp === 0) {\n        this._totalBlockingDuration += entry.blockingDuration;\n      }\n    }\n\n    try {\n      for (const script of entry.scripts) {\n        // sourceURL is an empty string for inline scripts\n        let url = script.sourceURL || '(inline)';\n        if (url.length > MAX_SCRIPT_URL_LENGTH) {\n          url = `${url.substring(0, MAX_SCRIPT_URL_LENGTH)}...`;\n        }\n\n        const existing = this._scriptSummaries.get(url);\n        if (existing) {\n          existing.totalDuration += script.duration;\n          existing.styleAndLayoutDuration +=\n            script.forcedStyleAndLayoutDuration;\n          existing.count++;\n        } else {\n          this._scriptSummaries.set(url, {\n            totalDuration: script.duration,\n            styleAndLayoutDuration: script.forcedStyleAndLayoutDuration,\n            count: 1,\n          });\n        }\n      }\n    } catch (e) {\n      this._diag.error('error processing scripts for entry', e);\n    }\n  }\n\n  private _flushReport(): void {\n    if (this._count === 0) {\n      return;\n    }\n\n    // use web-vitals Metric type for rating\n    let rating: Metric['rating'];\n    if (this._totalBlockingDuration <= BLOCKING_DURATION_GOOD_THRESHOLD) {\n      rating = 'good';\n    } else if (\n      this._totalBlockingDuration <= BLOCKING_DURATION_POOR_THRESHOLD\n    ) {\n      rating = 'needs-improvement';\n    } else {\n      rating = 'poor';\n    }\n\n    const attrs: Record<string, string | number> = {\n      [KEY_EMB_TYPE]: EMB_TYPES.WebVital,\n      ['emb.web_vital.id']: generateWebVitalID(),\n      ['emb.web_vital.name']: 'TBD',\n      ['emb.web_vital.value']: Math.round(this._totalBlockingDuration),\n      ['emb.web_vital.rating']: rating,\n      [ATTR_TBD_LOAF_TOTAL_DURATION]: Math.round(this._totalDuration),\n      [ATTR_TBD_LOAF_WORK_DURATION]: Math.round(this._workDuration),\n      [ATTR_TBD_LOAF_STYLE_AND_LAYOUT_DURATION]: Math.round(\n        this._styleLayoutDuration,\n      ),\n      [ATTR_TBD_LOAF_COUNT]: this._count,\n      [ATTR_TBD_LOAF_LONGEST_DURATION]: Math.round(this._longestDuration),\n      [ATTR_TBD_LOAF_LONGEST_DURATION_EXCLUDING_FIRST]: Math.round(\n        this._longestDurationExcludingFirst,\n      ),\n    };\n\n    try {\n      this.logger.emit({\n        eventName: LOAF_EVENT_NAME,\n        severityNumber: SeverityNumber.INFO,\n        attributes: attrs,\n      });\n    } catch (e) {\n      this._diag.error('error emitting loaf report', e);\n    }\n\n    try {\n      if (this._scriptSummaries.size > 0) {\n        const scripts = [...this._scriptSummaries]\n          .sort((a, b) => b[1].totalDuration - a[1].totalDuration)\n          .slice(0, MAX_SCRIPT_ENTRIES)\n          .map(([url, script]) => [\n            url,\n            {\n              total_duration: Math.round(script.totalDuration),\n              style_and_layout_duration: Math.round(\n                script.styleAndLayoutDuration,\n              ),\n              count: script.count,\n            },\n          ]);\n\n        this.logger.emit({\n          eventName: LOAF_SCRIPTS_EVENT_NAME,\n          severityNumber: SeverityNumber.INFO,\n          body: JSON.stringify(Object.fromEntries(scripts)),\n          attributes: {\n            [KEY_EMB_TYPE]: EMB_TYPES.LoafScripts,\n          },\n        });\n      }\n    } catch (e) {\n      this._diag.error('error emitting loaf script summary', e);\n    }\n\n    this._resetAccumulators();\n  }\n\n  private _resetAccumulators(): void {\n    this._totalDuration = 0;\n    this._workDuration = 0;\n    this._styleLayoutDuration = 0;\n    this._count = 0;\n    this._longestDuration = 0;\n    this._longestDurationExcludingFirst = 0;\n    this._totalBlockingDuration = 0;\n    this._scriptSummaries = new Map();\n  }\n}\n"],"mappings":"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"}