Perfume is a tiny, web performance monitoring library which reports field data like Navigation Timing, Resource Timing, First Contentful Paint (FP/FCP), Largest Contentful Paint (LCP), First Input Delay (FID) back to your favorite analytics tool.
English | 简体中文
- ⏰ Supported latest Performance APIs for precise metrics
- 🔨 Cross browser tested
- 🚿 Filters out false positive/negative results
- 🤙 Only 2Kb gzip
- 🛰 Flexible analytics tool
- ⚡️ Waste-zero ms with requestIdleCallback strategy built-in
Perfume leverage the latest W3C Performance Drafts (e.g. PerformanceObserver), for measuring performance that matters! Also known as field data, they allow to understand what real-world users are actually experiencing.
- Navigation Timing
- Resource Timing
- First Paint (FP)
- First Contentful Paint (FCP)
- Largest Contentful Paint (LCP)
- First Input Delay (FID)
- Framework components lifecycle monitoring
With Perfume.js, you can collect those metrics and have a deep understanding everywhere in the world how your customers perceive web performance for your application. Use your favorite analytics tool to visualize the data between countries,here below how it might look a sample data for FCP between the United States, Italy, Indonesia, and Nigeria.
npm (https://www.npmjs.com/package/perfume.js):
npm install perfume.js --save
You can import the generated bundle to use the whole library generated:
import Perfume from 'perfume.js';
Universal Module Definition:
import Perfume from 'node_modules/perfume.js/dist/perfume.umd.min.js';
Navigation Timing collects performance metrics for the life and timings of a network request. Perfume helps expose some of the key metrics you might need.
- DNS lookup: When a user requests a URL, the Domain Name System (DNS) is queried to translate a domain to an IP address.
- Header size: HTTP header size
- Fetch time: Cache seek plus response time
- Worker time: Service worker time plus response time
- Total time: Request plus response time (network only)
- Download time: Response time only (download)
- Time to First Byte: 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.
const perfume = new Perfume({
navigationTiming: true,
analyticsTracker: ({ metricName, data }) => {
myAnalyticsTool.track(metricName, data);
})
});
// Perfume.js: NavigationTiming {{'{'}} ... timeToFirstByte: 192.65 {{'}'}}
Resource Timing collects performance metrics for document-dependent resources. Stuff like style sheets, scripts, images, et cetera. Perfume helps expose all PerformanceResourceTiming entries and group data data consumption by Kb used.
const perfume = new Perfume({
resourceTiming: true,
dataConsumption: true,
analyticsTracker: ({ metricName, data }) => {
myAnalyticsTool.track(metricName, data);
})
});
// Perfume.js: dataConsumption { "css": 185.95, "fetch": 0, "img": 377.93, ... , "script": 8344.95 }
First Paint (FP)
FP is the exact time the browser renders anything as visually different from what was on the screen before navigation, e.g. a background change after a long blank white screen time.
const perfume = new Perfume({
firstPaint: true,
analyticsTracker: ({ metricName, duration }) => {
myAnalyticsTool.track(metricName, duration);
})
});
// Perfume.js: First Paint 1482.00 ms
First Contentful Paint (FCP)
FCP is the exact time the browser renders 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.
const perfume = new Perfume({
firstContentfulPaint: true,
analyticsTracker: ({ metricName, duration }) => {
myAnalyticsTool.track(metricName, duration);
})
});
// Perfume.js: First Contentful Paint 2029.00 ms
Largest Contentful Paint (LCP) 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.
const perfume = new Perfume({
largestContentfulPaint: true,
analyticsTracker: ({ metricName, duration }) => {
myAnalyticsTool.track(metricName, duration);
})
});
// Perfume.js: Largest Contentful Paint 2429.00 ms
FID 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..
const perfume = new Perfume({
firstInputDelay: true,
analyticsTracker: ({ metricName, duration }) => {
myAnalyticsTool.track(metricName, duration);
})
});
// Perfume.js: First Input Delay 3.20 ms
Performance.mark (User Timing API) is used to create an application-defined peformance entry in the browser's performance entry buffer.
const perfume = new Perfume({
analyticsTracker: ({ metricName, duration }) => {
myAnalyticsTool.track(metricName, duration);
})
});
perfume.start('fibonacci');
fibonacci(400);
perfume.end('fibonacci');
// Perfume.js: fibonacci 0.14 ms
This metric mark the point, immediately after creating a new component, when the browser renders pixels to the screen.
const perfume = new Perfume({
analyticsTracker: ({ metricName, duration }) => {
myAnalyticsTool.track(metricName, duration);
})
});
perfume.start('togglePopover');
$(element).popover('toggle');
perfume.endPaint('togglePopover');
// Perfume.js: togglePopover 10.54 ms
Save the duration and print it out exactly the way you want it.
const perfume = new Perfume({
analyticsTracker: ({ metricName, duration }) => {
myAnalyticsTool.track(metricName, duration);
}),
logPrefix: '🍹 HayesValley.js:'
});
perfume.start('fibonacci');
fibonacci(400);
const duration = perfume.end('fibonacci');
// 🍹 HayesValley.js: Custom logging 0.14 ms
Wth the Angular framework, we can start configuring Perfume to collect the initial performance metrics (eg. FCP, FID). Make sure to import the PefumeModule
at first inside the NgModule
to let the PerformanceObserver work correctly.
In a large application use the @PerfumeAfterViewInit()
decorator to monitor the rendering performance of the most complex components. Avoid using it inside a NgFor, instead focus on components that include a collection of smaller components.
The NgPerfume
service exposes all the methods and property of the perfume
instance, you can annotate component lifecycles combined with APIs calls to measure how long it takes to paint the component.
import { NgPerfume, PerfumeModule, PerfumeAfterViewInit } from 'perfume.js/angular';
import { AppComponent } from './app.component';
import { AppApi } from './app-api';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less'],
})
@PerfumeAfterViewInit('AppComponent')
export class AppComponent implements AfterViewInit {
data: AwesomeType;
constructor(public perfume: NgPerfume) {
// Start measure component time to paint
this.perfume.start('AppComponentAfterPaint');
}
ngAfterViewInit() {
this.loadAwesomeData();
}
loadAwesomeData = async () => {
await AppApi.loadAmazingData();
this.data = AppApi.loadAwesomeData();
// End measure component time to paint
this.perfume.endPaint('AppComponentAfterPaint');
}
}
// Perfume.js config, supports AOT and DI
const analyticsTracker = function ({ metricName, data, duration }) {
switch(metricName) {
case 'navigationTiming':
myAnalyticsTool.track(metricName, data);
break;
case 'resourceTiming':
myAnalyticsTool.track(metricName, data);
break;
case 'dataConsumption':
myAnalyticsTool.track(metricName, data);
break;
default:
myAnalyticsTool.track(metricName, duration);
break;
}
})
export const PerfumeConfig = {
firstContentfulPaint: true,
firstInputDelay: true,
dataConsumption: true,
navigationTiming: true,
resourceTiming: true,
analyticsTracker,
};
@NgModule({
declarations: [AppComponent],
imports: [PerfumeModule.forRoot(PerfumeConfig), BrowserModule],
bootstrap: [AppComponent],
})
export class AppModule {}
In combination with the React framework, we can start configuring Perfume to collect the initial performance metrics (eg. FCP, FID).
Use perfume.start()
and perfume.endPaint()
into a component lifecycle mixed with APIs calls to measure how long it takes to paint the component.
import React from 'react';
import Perfume from 'perfume.js';
import { AppApi } from './AppApi';
const analyticsTracker = function ({ metricName, data, duration }) {
switch(metricName) {
case 'navigationTiming':
myAnalyticsTool.track(metricName, data);
break;
case 'resourceTiming':
myAnalyticsTool.track(metricName, data);
break;
case 'dataConsumption':
myAnalyticsTool.track(metricName, data);
break;
default:
myAnalyticsTool.track(metricName, duration);
break;
}
})
const perfume = new Perfume({
firstContentfulPaint: true,
firstInputDelay: true,
dataConsumption: true,
navigationTiming: true,
resourceTiming: true,
analyticsTracker,
});
export default class App extends React.Component {
constructor() {
// Start measure component time to paint
perfume.start('AppAfterPaint');
}
loadData = async () => {
await AppApi.loadAmazingData();
await AppApi.loadAwesomeData();
// End measure component time to paint
perfume.endPaint('AppAfterPaint');
}
render() {
const data = this.loadData();
return (
<div>
<h2>Awesome App</h2>
<div>{data}</div>
</div>
);
}
}
Configurable analytics callback to use Perfume.js with any platform.
const perfume = new Perfume({
analyticsTracker: ({ metricName, data, duration }) => {
myAnalyticsTool.track(metricName, data, duration);
})
});
Default options provided to Perfume.js constructor.
const options = {
// Metrics
firstContentfulPaint: false,
firstPaint: false,
firstInputDelay: false,
dataConsumption: false,
largestContentfulPaint: false,
navigationTiming: false,
resourceTiming: false,
// Analytics
analyticsTracker: options => {},
// Logging
logPrefix: "Perfume.js:"
logging: true,
maxMeasureTime: 15000,
warning: false,
};
npm run test
: Run test suitenpm run build
: Generate bundles and typingsnpm run lint
: Lints code
Made with ☕️ by @zizzamia and I want to thank some friends and projects for the work they did:
- Leveraging the Performance Metrics that Most Affect User Experience for documenting this new User-centric performance metrics;
- Performance Timeline Level 2 the definition of PerformanceObserver in that specification;
- The Contributors for their much appreciated Pull Requests and bug reports;
- you for the star you'll give this project 😉 and for supporting me by giving my project a try 😄
This project exists thanks to all the people who contribute.
Thank you to all our backers! 🙏 [Become a backer]
Code and documentation copyright 2019 Leonardo Zizzamia. Code released under the MIT license. Docs released under Creative Commons.
Leonardo Zizzamia |