Zizzamia/perfume.js

View on GitHub
docs/src/app/app.component.html

Summary

Maintainability
Test Coverage
<div class="box">
  <h3 class="box-title" id="/quick-start/">
    <a href="{{ path }}#/quick-start/">
      Quick Start
    </a>
  </h3>
  <p>
    Metrics like Navigation Timing, Network Information, FCP, FID, LCP, CLS
    and TBT are default reported with Perfume; All results will be reported to
    the analyticsTracker callback, and the code below is just one way for you to
    organize your tracking, feel free to tweak it suit your needs.
  </p>
  <p class="box-demo">
    Oh btw, you're being served a
    <b *ngIf="isLowEndExperience">lighter experience</b
    ><b *ngIf="!isLowEndExperience">full experience</b> based on your hardware,
    network conditions and data saver preference.<br /><br />

    <b>// Device Information</b><br />
    Network information: <b>{{ networkInformation?.effectiveType || '--' }}</b
    ><br />
    Device Memory: <b>{{ navigatorInformation?.deviceMemory || '--' }}</b
    ><br />
    Hardware Concurrency:
    <b>{{ navigatorInformation?.hardwareConcurrency || '--' }}</b
    ><br />
    Data Saver preference: <b>{{ networkInformation?.saveData }}</b
    ><br />
    <br />
    <b>// Main Performance Metrics</b><br />
    Time To First Byte: <b>{{ ttfb || 0 }}ms</b
    ><br />
    First Contentful Paint: <b>{{ fcp }} ms</b><br />
    First Input Delay: <b>{{ fid }} ms</b><br />
    Largest Contentful Paint: <b>{{ lcp }} ms</b><br />
    Cumulative Layout Shift: <b>{{ cls }} score</b><br />
    Total Blocking Time: <b>{{ tbt }} ms</b><br /><br />

    <b>// Secondary Performance Metrics</b><br />
    <b
      >// These metrics activate on a set delay or when the tab changes
      visibility</b
    ><br />

    <b>// Experimental Performance metrics</b><br />
    dataConsumption: <b>{{ dataConsumption }}</b><br />
    Hero logo element timing: <b>{{ elHeroLogo }} ms</b><br />
    Page title element timing: <b>{{ elPageTitle }} ms</b>
  </p>
  <div class="box-code js">
    <table>
      <tbody>
        <tr>
          <td class="code">
            <pre><span class="red">const</span> <span class="blue">perfume</span> <span class="red">= new</span> <span class="violet">Perfume</span>({{'{'}}
  <span class="violet">analyticsTracker</span><span class="red">:</span> (options) <span class="red">=></span> {{'{'}}
    <span class="red">const</span> {{'{'}} metricName, data, navigatorInformation {{'}'}} <span class="red">=</span> options;
    <span class="red">switch</span> (metricName) {{'{'}}
      <span class="red">case</span> 'navigationTiming'<span class="red">:</span>
        if (data && data.timeToFirstByte) {{'{'}}
          myAnalyticsTool.<span class="violet">track</span>('navigationTiming', data);
        {{'}'}}
        <span class="red">break</span>;
      <span class="red">case</span> 'networkInformation'<span class="red">:</span>
        if (data && data.effectiveType) {{'{'}}
          myAnalyticsTool.<span class="violet">track</span>('networkInformation', data);
        {{'}'}}
        <span class="red">break</span>;
      <span class="red">case</span> 'TTFB'<span class="red">:</span>
        myAnalyticsTool.<span class="violet">track</span>('timeToFirstByte', {{'{'}} duration: data {{'}'}});
        <span class="red">break</span>;
      <span class="red">case</span> 'FCP'<span class="red">:</span>
        myAnalyticsTool.<span class="violet">track</span>('firstContentfulPaint', {{'{'}} duration: data {{'}'}});
        <span class="red">break</span>;
      <span class="red">case</span> 'FID'<span class="red">:</span>
        myAnalyticsTool.<span class="violet">track</span>('firstInputDelay', {{'{'}} duration: data {{'}'}});
        <span class="red">break</span>;
      <span class="red">case</span> 'LCP'<span class="red">:</span>
        myAnalyticsTool.<span class="violet">track</span>('largestContentfulPaint', {{'{'}} duration: data {{'}'}});
        <span class="red">break</span>;
      <span class="red">case</span> 'CLS'<span class="red">:</span>
        myAnalyticsTool.<span class="violet">track</span>('cumulativeLayoutShift', {{'{'}} duration: data {{'}'}});
        <span class="red">break</span>;
      <span class="red">case</span> 'TBT'<span class="red">:</span>
        myAnalyticsTool.<span class="violet">track</span>('totalBlockingTime', {{'{'}} duration: data {{'}'}});
        <span class="red">break</span>;
      <span class="red">default</span>:
        myAnalyticsTool.<span class="violet">track</span>(metricName, {{'{'}} duration: data {{'}'}});
        <span class="red">break</span>;
    {{'}'}}
  {{'}'}},
{{'}'}});</pre>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
  <br />
  <p>
    In a world with widely varying device capabilities, a one-size-fits-all
    event doesn’t always work. Perfume adds <b>data enrichment</b> to all events
    so we can better understand the real world experiences:
  </p>
  <ul>
    <li><b>deviceMemory</b>: the user's device memory (RAM).</li>
    <li>
      <b>hardwareConcurrency</b>: the number of logical CPU processor cores on
      the user's device.
    </li>
    <li>
      <b>serviceWorkerStatus</b>: status of the service worker between
      controlled, supported and unsupported.
    </li>
  </ul>
  <p>
    Based on the Navigator APIs the library can help us differentiate between a
    low-end and a high-end device/experience:
  </p>
  <ul>
    <li><b>isLowEndDevice</b>: combination of the score of RAM and CPU.</li>
    <li>
      <b>isLowEndExperience</b>: combination of the score of RAM, CPU,
      NetworkStatus and SaveData.
    </li>
  </ul>
</div>

<div class="box">
  <h2 class="box-title">Performance audits</h2>
  <p>
    Coo coo coo
    <a href="https://www.youtube.com/watch?v=zDcbpFimUc8" rel="noreferrer"
    target="_blank">cool</a>, let's learn something new about measuring <b>page's speed</b> performance.
  </p>
</div>

<div class="box">
  <h3 class="box-title" id="/navigation-timing/">
    <a href="{{ path }}#/navigation-timing/">
      Navigation Timing
    </a>
  </h3>
  <p>
    Navigation Timing collects performance metrics for the life and timings of a
    network request.
  </p>
  <p>Perfume helps expose some of the key metrics you might need.</p>
  <ul>
    <li>
      <b>DNS lookup</b>: When a user requests a URL, the Domain Name System
      (DNS) is queried to translate a domain to an IP address.
    </li>
    <li><b>Header size</b>: HTTP header size</li>
    <li><b>Fetch time</b>: Cache seek plus response time</li>
    <li><b>Worker time</b>: Service worker time plus response time</li>
    <li><b>Total time</b>: Request plus response time (network only)</li>
    <li><b>Download time</b>: Response time only (download)</li>
    <li>
      <b>Time to First Byte</b>: The amount of time it takes after the client
      sends an HTTP GET request to receive the first byte of the requested
      resource from the server. It is the largest web page load time component
      taking 40 to 60% of total web page latency.
    </li>
  </ul>
  <p class="box-demo">
    <b>Navigation Timing</b>: {{ navigationTiming }}
  </p>
</div>

<div class="box">
  <h3 class="box-title" id="/first-contentful-paint/">
    <a href="{{ path }}#/first-contentful-paint/">
      First Contentful Paint (FCP)
    </a>
  </h3>
  <p>
    <b>First Contentful Paint</b> is the exact time taken for the browser to render the first bit
    of content from the DOM, which can be anything from an important image,
    text, or even the small SVG at the bottom of the page.
  </p>
  <p class="box-demo"><b>First Contentful Paint</b>: {{ fcp }} ms</p>
</div>

<div class="box">
  <h3 class="box-title" id="/largest-contentful-paint/">
    <a href="{{ path }}#/largest-contentful-paint/">
      Largest Contentful Paint (LCP)
    </a>
  </h3>
  <p>
    <b>Largest Contentful Paint</b> is an important, user-centric metric for measuring perceived load
    speed because it marks the point in the page load timeline when the page's
    main content has likely loaded—a fast LCP helps reassure the user that the
    page is useful.
  </p>
  <p>
    We end the Largest Contentful Paint measure at two points: when First Input
    Delay happen and when the page's lifecycle state changes to hidden.
  </p>
  <p class="box-demo">
    <b>Largest Contentful Paint:</b> {{ lcp }} ms<br />
  </p>
</div>

<div class="box">
  <h3 class="box-title" id="/first-input-delay/">
    <a href="{{ path }}#/first-input-delay/">First Input Delay (FID)</a>
  </h3>
  <p>
    <b>First Input Delay</b> measures the time from when a user first interacts with your site
    (i.e. when they click a link, tap on a button) to the time when the browser
    is actually able to respond to that interaction.
  </p>
  <p class="box-demo"><b>First Input Delay</b>: {{ fid }} ms</p>
</div>

<div class="box">
  <h3 class="box-title" id="/cumulative-layout-shift/">
    <a href="{{ path }}#/cumulative-layout-shift/">Cumulative Layout Shift (CLS)</a>
  </h3>
  <p>
    <b>Cumulative Layout Shift</b> is an important, user-centric metric for measuring visual
    stability because it helps quantify how often users experience unexpected
    layout shifts—a low CLS helps ensure that the page is delightful.
  </p>
  <p class="box-demo">
    <b>Cumulative Layout Shift</b>: {{ cls }}<br />
  </p>
</div>

<div class="box">
  <h3 class="box-title" id="/total-blocking-time/">
    <a href="{{ path }}#/total-blocking-time/">Total Blocking Time (TBT)</a>
  </h3>
  <p>
    <b>Total Blocking Time</b> is an important, user-centric metric for measuring load
    responsiveness because it helps quantify the severity of how non-interactive
    a page is prior to it becoming reliably interactive—a low TBT helps ensure
    that the page is usable.
  </p>
  <p>
    We end the Total Blocking Time measure 10 seconds after FID.
  </p>
  <p class="box-demo">
    <b>Total Blocking Time</b>: {{ tbt }} ms<br />
  </p>
</div>

<div class="box">
  <h3 class="box-title" id="/navigation-total-blocking-time/">
    <a href="{{ path }}#/navigation-total-blocking-time/">Navigation Total Blocking Time (NTBT)</a>
  </h3>
  <p>
    This metric measures the amount of time the application may be blocked from processing code during the 2s window after a user navigates from page A to page B. The NTBT metric is the summation of the blocking time of all long tasks in the 2s window after this method is invoked.
  </p>
  <p>Because this library is navigation agnostic, we have this method to mark when the navigation starts.</p>
  <p>If this method is called before the 2s window ends; it will trigger a new NTBT measurement and interrupt the previous one.</p>
  <p class="box-demo">
    <button class="box-demo-btn" (click)="measureNTBT()">
      Simulate Navigation Total Blocking Time
    </button>
    <span> {{ ntbt }}ms</span>
  </p>
</div>

<div class="box">
  <h3 class="box-title" id="/resource-timing/">
    <a href="{{ path }}#/resource-timing/">
      Resource Timing
    </a>
  </h3>
  <p>
    Resource Timing collects performance metrics for document-dependent
    resources. Stuff like style sheets, scripts, images, et cetera.
  </p>
  <p>
    Perfume helps expose all PerformanceResourceTiming entries and groups data
    <b>data consumption</b> by Kb used.
  </p>
  <p class="box-demo">
    <b>Data Consumption</b>: {{ dataConsumption }}
  </p>
  <div class="box-code js">
    <table>
      <tbody>
        <tr>
          <td class="code">
            <pre><span class="red">const</span> <span class="blue">perfume</span> <span class="red">= new</span> <span class="violet">Perfume</span>({{'{'}}
  resourceTiming<span class="red">:</span> <span class="blue">true</span>,
  <span class="violet">analyticsTracker</span><span class="red">:</span> ({{'{'}} metricName, data {{'}'}}) => {{'{'}}
    myAnalyticsTool.<span class="violet">track</span>(metricName, data);
  {{'}'}})
{{'}'}});</pre>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

<div class="box">
  <h3 class="box-title" id="/annotate-metrics/">
    <a href="{{ path }}#/annotate-metrics/">Annotate metrics in the DevTools</a>
  </h3>
  <p>
    <b>Performance.mark</b> (
    <a
      href="https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API"
      target="_blank" rel="noreferrer"
      >User Timing API</a
    >) is used to create an application-defined peformance entry in the
    browser's performance entry buffer.
  </p>
  <p class="box-demo">
    <button class="box-demo-btn" (click)="measureFibonacci()">
      Run Fibonacci
    </button>
    <span>{{ logFibonacci }}</span>
  </p>
  <div class="box-code js">
    <table>
      <tbody>
        <tr>
          <td class="code">
            <pre><span class="red">const</span> <span class="blue">perfume</span> <span class="red">= new</span> <span class="violet">Perfume</span>({{'{'}}
  <span class="violet">analyticsTracker</span><span class="red">:</span> ({{'{'}} metricName, data {{'}'}}) => {{'{'}}
    myAnalyticsTool.<span class="violet">track</span>(metricName, data);
  {{'}'}})
{{'}'}});
perfume.<span class="blue">start</span>('fibonacci');
<span class="violet">fibonacci</span>(<span class="blue">400</span>);
perfume.<span class="violet">end</span>('fibonacci');</pre>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
  <picture class="box-img no-mobile">
    <source srcset="{{ path }}assets/performance-mark.webp" type="image/webp" class="no-mobile" />
    <source srcset="{{ path }}assets/performance-mark.png" type="image/png" class="no-mobile" />
    <img
      src="{{ path }}assets/performance-mark.png"
      alt="Annotate metrics in the DevTools"
      loading="lazy" class="no-mobile"
    />
  </picture>
</div>

<div class="box">
  <h3 class="box-title" id="/component-first-paint/">
    <a href="{{ path }}#/component-first-paint/">Component First Paint</a>
  </h3>
  <p>Use perfume.endPaint() to mark the point, immediately after the browser renders pixels to the screen.</p>
  <div class="box-code js">
    <table>
      <tbody>
        <tr>
          <td class="code">
            <pre><span class="red">const</span> <span class="blue">perfume</span> <span class="red">= new</span> <span class="violet">Perfume</span>({{'{'}}
    <span class="violet">analyticsTracker</span><span class="red">:</span> ({{'{'}} metricName, duration {{'}'}}) => {{'{'}}
      myAnalyticsTool.<span class="violet">track</span>(metricName, duration);
    {{'}'}})
  {{'}'}});
  perfume.<span class="blue">start</span>('openDialog');
  <span class="violet">this</span>.dialog.<span class="violet">open</span>();
  perfume.<span class="violet">endPaint</span>('openDialog');</pre>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
  <picture class="box-img no-mobile">
    <source srcset="{{ path }}assets/performance-cfp.webp" type="image/webp" class="no-mobile" />
    <source srcset="{{ path }}assets/performance-cfp.png" type="image/png" class="no-mobile" />
    <img src="{{ path }}assets/performance-cfp.png"
    alt="Annotate Component First Paint (CFP)" loading="lazy" class="no-mobile" />
  </picture>
</div>
<div class="box">
  <h3 class="box-title" id="/element-timing">
    <a href="{{ path }}#/element-timing">Element Timing</a>
  </h3>
  <p>Add the <b>elementtiming</b> attribute to track when image elements and text nodes with are displayed on screen using the <a href="https://wicg.github.io/element-timing/" target="_blank" rel="noreferrer">Element Timing API</a></p>
  <p class="box-demo">
    <b>Hero logo element timing</b>: {{ elHeroLogo }} ms
    <br />
    <b>Page title element timing</b>: {{ elPageTitle }} ms
  </p>
  <div class="box-code html">
    <table>
      <tbody>
        <tr>
          <td class="code">
            <pre><span class="violet">{{'<'}}h1</span> <span class="blue">elementtiming</span><span class="violet">="</span><span class="red">page-title</span><span class="violet">" </span><span class="blue">class</span><span class="violet">="</span><span class="red">title</span><span class="violet">"{{'>'}}</span>Perfume.js<span class="violet">{{'<'}}/h1{{'>'}}</span></pre>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
<div class="box">
  <h3 class="box-title" id="/user-journey-step-tracking">
    <a href="{{ path }}#/user-journey-step-tracking">User Journey Step Tracking</a>
  </h3>
  <p>
    A Step represents a slice of time in the User Journey where the user is blocked by system time. System time is time the system is blocking the user. For example, the time it takes to navigate between screens or fetch critical information from the server. This should not be confused with cognitive time, which is the time the user spends thinking about what to do next. User Journey steps should only cover system time.
  </p>
  <p>A Step is defined by an event to start the step, and another event to end the step. These events are referred to as <b>Marks</b>.</p>
  <p>As an example, a Step could be to navigate from screen A to screen B. The appropriate way to mark a start and end to this step is by marking the start when tapping on the button on screen A that starts the navigation and marking the end when screen B comes into focus with the critical data rendered on the screen.</p>
  <div class="box-code js">
    <table>
      <tbody>
        <tr>
          <td class="code">
            <pre>
<span class="gray">// Marking the start of the step</span>
<span class="red">const</span> <span class="blue">ScreenA</span> <span class="red">= () =></span> ({{'{'}}
  <span class="red">const</span> <span class="blue">handleNavigation</span> <span class="red">= () =></span> {{'{'}}
    <span class="gray">// Navigation logic</span>
    <span class="gray">// Mark when navigating to screen B</span>
    <span class="violet">markStep</span><span>('navigate_to_screen_B')</span>
  }
  <span class="violet">return </span>(
    <span class="violet">{{'<'}}Button</span> <span class="blue">onPress</span><span class="violet">="</span><span class="red">{{'{'}}handleNavigation}</span><span class="violet">" </span><span class="violet">/></span>
  )
})

<span class="gray">// Marking the end of the step</span>
<span class="red">const</span> <span class="blue">ScreenB</span> <span class="red">= () =></span> ({{'{'}}
  <span class="red">const</span> <span class="blue">{{'{'}} data }</span> <span class="red">=</span> <span class="violet">fetch</span><span>("http://example.com/data")</span>

  <span class="violet">useEffect</span><span>(() =></span> {{'{'}}
    <span class="red">if</span> <span>(data)</span> {{'{'}}
      <span class="violet">markStep</span><span>('loaded_screen_B')</span>
    }
  },[<span class="blue">data</span>])
})</pre>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>