Comparing version 0.14.3 to 1.0.0
@@ -10,3 +10,3 @@ #!/usr/bin/env node | ||
const argv = minimist(process.argv.slice(2), { | ||
boolean: ['pretty', 'includeAssets'], | ||
boolean: ['pretty', 'includeAssets'] | ||
}); | ||
@@ -19,4 +19,8 @@ | ||
console.log(' --pretty Pretty format the JSON [false]'); | ||
console.log(' --includeAssets Include info about every asset in the result [false]'); | ||
console.log(' --firstParty A regex defining if a URL is 1st or 3rd party URL'); | ||
console.log( | ||
' --includeAssets Include info about every asset in the result [false]' | ||
); | ||
console.log( | ||
' --firstParty A regex defining if a URL is 1st or 3rd party URL' | ||
); | ||
} else { | ||
@@ -23,0 +27,0 @@ const har = JSON.parse(fs.readFileSync(argv._[0])); |
# CHANGELOG - PageXray | ||
version 1.0.0 2017-08-26 | ||
------------------------ | ||
### Added | ||
* You can now run PageXray in the browser! Check Github releases to download that version. | ||
* If the HAR is generated with sitespeed.io > 5.4.3, we pickup some extra meta data: connection type, URL to the result page, URL to the video, URL to the screenshot. And we also add the browser and version if that is availible. | ||
* Pickup firstParty/thirdParty config from the HAR, override with config. | ||
* Collect timings per domain. | ||
* Collect timings per timing type instead of total. This change is not backward compatible and you need to calculate the total yourself. | ||
version 0.14.3 2017-06-23 | ||
@@ -4,0 +13,0 @@ ------------------------ |
@@ -7,3 +7,5 @@ 'use strict'; | ||
function getTiming(entry, field) { | ||
return entry.timings[field] === -1 ? 0 : entry.timings[field]; | ||
return entry.timings[field] === -1 | ||
? 0 | ||
: Number(entry.timings[field].toFixed(0)); | ||
} | ||
@@ -16,6 +18,19 @@ | ||
headerSize: 0, | ||
requests: 0, | ||
requests: 0 | ||
}; | ||
} | ||
function newContentDataWithTimings() { | ||
const content = newContentData(); | ||
content.timings = { | ||
blocked: 0, | ||
dns: 0, | ||
connect: 0, | ||
send: 0, | ||
wait: 0, | ||
receive: 0 | ||
}; | ||
return content; | ||
} | ||
/* | ||
@@ -28,4 +43,3 @@ * Collect information about a response (asset). | ||
module.exports = { | ||
asset: (entry) => { | ||
asset: entry => { | ||
const response = entry.response; | ||
@@ -38,6 +52,10 @@ const request = entry.request; | ||
const timing = getTiming(entry, 'blocked') + getTiming(entry, 'dns') + | ||
getTiming(entry, 'connect') + | ||
getTiming(entry, 'send') + getTiming(entry, 'wait') + | ||
getTiming(entry, 'receive'); | ||
const timings = { | ||
blocked: getTiming(entry, 'blocked'), | ||
dns: getTiming(entry, 'dns'), | ||
connect: getTiming(entry, 'connect'), | ||
send: getTiming(entry, 'send'), | ||
wait: getTiming(entry, 'wait'), | ||
receive: getTiming(entry, 'receive') | ||
}; | ||
@@ -48,3 +66,4 @@ return { | ||
transferSize: response.bodySize, | ||
contentSize: response.content.size < 0 ? response.bodySize : response.content.size, | ||
contentSize: | ||
response.content.size < 0 ? response.bodySize : response.content.size, | ||
headerSize: response.headersSize, | ||
@@ -57,6 +76,6 @@ expires: headers.getExpires(responseHeaders), | ||
request: requestHeaders, | ||
response: responseHeaders, | ||
response: responseHeaders | ||
}, | ||
timing, | ||
cookies: request.cookies.length, | ||
timings, | ||
cookies: request.cookies.length | ||
}; | ||
@@ -75,3 +94,3 @@ }, | ||
const domain = util.getHostname(asset.url); | ||
const contentData = domains[domain] || newContentData(); | ||
const contentData = domains[domain] || newContentDataWithTimings(); | ||
@@ -83,2 +102,9 @@ contentData.transferSize += asset.transferSize; | ||
contentData.timings.blocked += asset.timings.blocked; | ||
contentData.timings.dns += asset.timings.dns; | ||
contentData.timings.connect += asset.timings.connect; | ||
contentData.timings.send += asset.timings.send; | ||
contentData.timings.wait += asset.timings.wait; | ||
contentData.timings.receive += asset.timings.receive; | ||
domains[domain] = contentData; | ||
@@ -105,3 +131,10 @@ }, | ||
const couldBeCompressed = ['html', 'plain', 'json', 'javascript', 'css', 'svg'].includes(asset.type); | ||
const couldBeCompressed = [ | ||
'html', | ||
'plain', | ||
'json', | ||
'javascript', | ||
'css', | ||
'svg' | ||
].includes(asset.type); | ||
const isCompressed = ['gzip', 'br', 'deflate'].includes(encoding); | ||
@@ -123,3 +156,3 @@ const isLargeFile = asset.contentSize > 2000; | ||
image: newContentData(), | ||
font: newContentData(), | ||
font: newContentData() | ||
}; | ||
@@ -135,3 +168,3 @@ }, | ||
contentType: (asset, contentTypes) => { | ||
if (!(/^2\d{2}/.test(asset.status))) { | ||
if (!/^2\d{2}/.test(asset.status)) { | ||
return; | ||
@@ -152,3 +185,3 @@ } | ||
contentTypes[asset.type] = contentData; | ||
}, | ||
} | ||
}; |
'use strict'; | ||
module.exports = { | ||
/** | ||
@@ -12,3 +11,3 @@ * Flatten HAR file headers. Har files has the header of the | ||
*/ | ||
flatten: (headers) => { | ||
flatten: headers => { | ||
const theHeaders = headers.reduce((result, header) => { | ||
@@ -28,3 +27,3 @@ result[header.name.toLowerCase()] = header.value; | ||
*/ | ||
getExpires: (responseHeaders) => { | ||
getExpires: responseHeaders => { | ||
const maxAgeRegExp = /max-age=(\d+)/; | ||
@@ -34,4 +33,6 @@ let expireTime = 0; | ||
if (responseHeaders['cache-control']) { | ||
if (responseHeaders['cache-control'].indexOf('no-cache') !== -1 || | ||
responseHeaders['cache-control'].indexOf('no-store') !== -1) { | ||
if ( | ||
responseHeaders['cache-control'].indexOf('no-cache') !== -1 || | ||
responseHeaders['cache-control'].indexOf('no-store') !== -1 | ||
) { | ||
return 0; | ||
@@ -56,19 +57,17 @@ } | ||
*/ | ||
getTimeSinceLastModified: (headers) => { | ||
let now = new Date(); | ||
let lastModifiedDate; | ||
if (headers['last-modified']) { | ||
lastModifiedDate = new Date(headers['last-modified']); | ||
} else if (headers.date) { | ||
now = new Date(headers.date); | ||
getTimeSinceLastModified: headers => { | ||
const lastModifiedHeader = headers['last-modified']; | ||
if (!lastModifiedHeader) { | ||
return -1; | ||
} | ||
// TODO how do we define if we don't have a timing | ||
// is it better to just return 0? | ||
if (!lastModifiedDate) { | ||
return -1; | ||
const lastModifiedMillis = Date.parse(lastModifiedHeader); | ||
let createdMillis = Date.now(); | ||
if (headers.date) { | ||
createdMillis = Date.parse(headers.date); | ||
} | ||
return (now.getTime() - lastModifiedDate.getTime()) / 1000; | ||
}, | ||
return (createdMillis - lastModifiedMillis) / 1000; | ||
} | ||
}; |
134
lib/index.js
@@ -5,4 +5,24 @@ 'use strict'; | ||
const collect = require('./collect'); | ||
const sitespeed = require('./sitespeed'); | ||
const webpagetest = require('./webpagetest'); | ||
const Statistics = require('./statistics').Statistics; | ||
function cleanupStatistics(pages, config, firstParty) { | ||
pages.forEach(page => { | ||
page.expireStats = page.expireStats.summarize(); | ||
page.lastModifiedStats = page.lastModifiedStats.summarize(); | ||
page.cookieStats = page.cookieStats.summarize(); | ||
page.totalDomains = Object.keys(page.domains).length; | ||
if (!config.includeAssets) { | ||
page.assets = []; | ||
} | ||
if (!firstParty) { | ||
page.firstParty = {}; | ||
page.thirdParty = {}; | ||
} else { | ||
page.firstParty.cookieStats = page.firstParty.cookieStats.summarize(); | ||
page.thirdParty.cookieStats = page.thirdParty.cookieStats.summarize(); | ||
} | ||
}); | ||
} | ||
/** | ||
@@ -14,2 +34,15 @@ * Convert a HAR object to a better page summary. | ||
module.exports = { | ||
/** | ||
* Convert one HAR to a page. Use this when you are interested | ||
* of one specific run in a HAR. | ||
* @param {Object} har The HAR to process. | ||
* @param {Number} index The index of the HAR file of the run that will be converted. | ||
* @param {Object} config The config object. | ||
* @returns {Array} The converted page object. | ||
*/ | ||
convertIndex: (har, index, config) => { | ||
// TODO in the future only convert that specific run to save time | ||
const pages = module.exports.convert(har, config); | ||
return pages[index]; | ||
}, | ||
@@ -29,11 +62,35 @@ /** | ||
const testedPages = {}; | ||
let firstParty; | ||
har.log.entries.forEach((entry) => { | ||
if ( | ||
config.firstParty || | ||
(har.log.pages[0]._meta && har.log.pages[0]._meta.firstParty) | ||
) { | ||
firstParty = config.firstParty || har.log.pages[0]._meta.firstParty; | ||
} | ||
function sortByTime(a, b) { | ||
return ( | ||
new Date(a.startedDateTime).getTime() - | ||
new Date(b.startedDateTime).getTime() | ||
); | ||
} | ||
har.log.entries.sort(sortByTime); | ||
har.log.entries.forEach(entry => { | ||
if (!testedPages[entry.pageref]) { | ||
const redirects = util.getFinalURL(entry, har); | ||
const httpVersion = | ||
redirects.chain.length === 0 | ||
? entry.response.httpVersion | ||
: util.getEntryByURL(har.log.entries, redirects.url).response | ||
.httpVersion; | ||
currentPage = { | ||
url: har.log.entries[0].request.url, | ||
url: entry.request.url, | ||
meta: { browser: {}, startedDateTime: entry.startedDateTime }, | ||
finalUrl: redirects.url, | ||
baseDomain: util.getHostname(redirects.url), | ||
documentRedirects: (redirects.chain.length === 0) ? 0 : (redirects.chain.length - 1), | ||
documentRedirects: | ||
redirects.chain.length === 0 ? 0 : redirects.chain.length - 1, | ||
redirectChain: redirects.chain, | ||
@@ -45,5 +102,4 @@ transferSize: 0, | ||
missingCompression: 0, | ||
// TODO this will not be right if redirected!!! | ||
httpType: util.getConnectionType(har.log.entries[0].response.httpVersion), | ||
httpVersion: util.getHTTPVersion(har.log.entries[0].response.httpVersion), | ||
httpType: util.getConnectionType(httpVersion), | ||
httpVersion: util.getHTTPVersion(httpVersion), | ||
contentTypes: collect.defaultContentTypes(), | ||
@@ -54,7 +110,7 @@ assets: [], | ||
cookieStats: new Statistics(), | ||
contentTypes: collect.defaultContentTypes(), | ||
contentTypes: collect.defaultContentTypes() | ||
}, | ||
thirdParty: { | ||
cookieStats: new Statistics(), | ||
contentTypes: collect.defaultContentTypes(), | ||
contentTypes: collect.defaultContentTypes() | ||
}, | ||
@@ -64,4 +120,12 @@ domains: {}, | ||
lastModifiedStats: new Statistics(), | ||
cookieStats: new Statistics(), | ||
cookieStats: new Statistics() | ||
}; | ||
if (har.log.browser && har.log.browser.name) { | ||
currentPage.meta.browser.name = har.log.browser.name; | ||
} | ||
if (har.log.browser && har.log.browser.version) { | ||
currentPage.meta.browser.version = har.log.browser.version; | ||
} | ||
testedPages[entry.pageref] = currentPage; | ||
@@ -84,8 +148,10 @@ pages.push(currentPage); | ||
currentPage.transferSize += entry.response.bodySize; | ||
currentPage.contentSize += entry.response.content.size < 0 ? entry.response.bodySize : | ||
entry.response.content.size; | ||
currentPage.headerSize += entry.response.headersSize; | ||
currentPage.contentSize += | ||
entry.response.content.size < 0 | ||
? entry.response.bodySize | ||
: entry.response.content.size; | ||
currentPage.headerSize += Math.max(entry.response.headersSize, 0); | ||
// add first/third party info | ||
if (config.firstParty) { | ||
if (firstParty) { | ||
// is it a third party asset? | ||
@@ -95,3 +161,3 @@ | ||
if (asset.url.match(config.firstParty)) { | ||
if (asset.url.match(firstParty)) { | ||
stats = currentPage.firstParty; | ||
@@ -101,8 +167,8 @@ } | ||
stats.requests = stats.requests + 1 || 1; | ||
stats.transferSize = stats.transferSize + asset.transferSize || | ||
asset.transferSize; | ||
stats.contentSize = stats.contentSize + asset.contentSize || | ||
asset.contentSize; | ||
stats.headerSize = stats.headerSize + asset.headerSize || | ||
asset.headerSize; | ||
stats.transferSize = | ||
stats.transferSize + asset.transferSize || asset.transferSize; | ||
stats.contentSize = | ||
stats.contentSize + asset.contentSize || asset.contentSize; | ||
stats.headerSize = | ||
stats.headerSize + asset.headerSize || asset.headerSize; | ||
stats.cookieStats.add(asset.cookies); | ||
@@ -116,20 +182,14 @@ collect.contentType(asset, stats.contentTypes); | ||
// cleanup the stats | ||
pages.forEach((page) => { | ||
page.expireStats = page.expireStats.summarize(); | ||
page.lastModifiedStats = page.lastModifiedStats.summarize(); | ||
page.cookieStats = page.cookieStats.summarize(); | ||
page.totalDomains = Object.keys(page.domains).length; | ||
if (!config.includeAssets) { | ||
page.assets = []; | ||
} | ||
if (!config.firstParty) { | ||
page.firstParty = {}; | ||
page.thirdParty = {}; | ||
} else { | ||
page.firstParty.cookieStats = page.firstParty.cookieStats.summarize(); | ||
page.thirdParty.cookieStats = page.thirdParty.cookieStats.summarize(); | ||
} | ||
}); | ||
cleanupStatistics(pages, config, firstParty); | ||
// If we have that extra meta field in the HAR, we are pretty sure | ||
// it is generated using sitespeed.io/browsertime, so add those | ||
// extra metrics | ||
if (har.log.pages[0]._meta) { | ||
sitespeed.addMetrics(har, pages); | ||
} else if (har.log.creator.name === 'WebPagetest') { | ||
webpagetest.addMetrics(har, pages); | ||
} | ||
return pages; | ||
}, | ||
} | ||
}; |
'use strict'; | ||
const Stats = require('fast-stats').Stats; | ||
function percentileName(percentile) { | ||
if (percentile === 0) { | ||
return 'min'; | ||
} else if (percentile === 100) { | ||
return 'max'; | ||
} else if (percentile === 50) { | ||
return 'median'; | ||
} | ||
return `p${String(percentile).replace('.', '_')}`; | ||
} | ||
class Statistics { | ||
constructor() { | ||
this.stats = new Stats(); | ||
this.values = []; | ||
this.total = 0; | ||
} | ||
add(value) { | ||
this.stats.push(value); | ||
this.values.push(value); | ||
this.total += value; | ||
return this; | ||
} | ||
summarize(options) { | ||
if (isNaN(this.stats.amean())) { | ||
summarize() { | ||
const values = this.values; | ||
// keeping backward compatibility | ||
if (values.length === 0) { | ||
return undefined; | ||
} | ||
options = options || {}; | ||
const percentiles = options.percentiles || [0, 10, 50, 90, 99, 100]; | ||
const decimals = options.decimals || 0; | ||
const data = {}; | ||
const stats = this.stats; | ||
values.sort((a, b) => a - b); | ||
percentiles.forEach((p) => { | ||
const name = percentileName(p); | ||
data[name] = Number(stats.percentile(p).toFixed(decimals)); | ||
}); | ||
const middle = Math.floor(values.length / 2); | ||
const isEven = values.length % 2 === 0; | ||
let median; | ||
if (isEven) { | ||
median = Number(((values[middle] + values[middle - 1]) / 2).toFixed(0)); | ||
} else { | ||
median = Number(values[middle].toFixed(0)); | ||
} | ||
return data; | ||
return { | ||
min: Number(values[0].toFixed(0)), | ||
median, | ||
max: Number(values[values.length - 1].toFixed(0)), | ||
total: Number(this.total.toFixed(0)), | ||
values: values.length | ||
}; | ||
} | ||
} | ||
module.exports = { | ||
Statistics, | ||
Statistics | ||
}; |
@@ -20,3 +20,3 @@ 'use strict'; | ||
[/^application\/ocsp-response/, 'oscp'], | ||
[/.*/, 'other'], // Always match 'other' if all else fails | ||
[/.*/, 'other'] // Always match 'other' if all else fails | ||
]; | ||
@@ -29,3 +29,2 @@ | ||
module.exports = { | ||
/** | ||
@@ -39,3 +38,3 @@ * Get the content type from mime type. | ||
getHTTPVersion: (version) => { | ||
getHTTPVersion: version => { | ||
if (version === 'h2' || version === 'HTTP/2.0') { | ||
@@ -48,3 +47,3 @@ return 'HTTP/2.0'; | ||
}, | ||
getConnectionType: (version) => { | ||
getConnectionType: version => { | ||
if (version === 'h2' || version === 'HTTP/2.0') { | ||
@@ -62,3 +61,3 @@ return 'h2'; | ||
*/ | ||
getHostname: (url) => { | ||
getHostname: url => { | ||
if (!url) { | ||
@@ -98,5 +97,12 @@ return ''; | ||
url, | ||
chain, | ||
chain | ||
}; | ||
}, | ||
getEntryByURL(entries, url) { | ||
for (let entry of entries) { | ||
if (entry.request.url === url) { | ||
return entry; | ||
} | ||
} | ||
} | ||
}; |
{ | ||
"name": "pagexray", | ||
"version": "0.14.3", | ||
"version": "1.0.0", | ||
"description": "Xray your HAR file and know all about the page", | ||
@@ -35,7 +35,11 @@ "keywords": [ | ||
"scripts": { | ||
"lint": "npm run eslint", | ||
"eslint": "eslint .", | ||
"lint": "eslint .", | ||
"lint:fix": "eslint . --fix", | ||
"eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check", | ||
"test": "mocha", | ||
"jsdoc": "jsdoc lib/* -d dist/doc", | ||
"travis": "npm run lint && npm run test" | ||
"travis": "npm run eslint-check && npm run lint && npm run test && npm run dist", | ||
"browserify": "mkdirp dist && browserify lib/index.js -o dist/pagexray.js --standalone PageXray -t [ babelify --presets [ es2015 ] ] -p [ browserify-banner --template 'PageXray v<%= pkg.version %> - Xray your HAR file and know all about the page - https://github.com/sitespeedio/pagexray' ]", | ||
"uglify": "uglifyjs dist/pagexray.js --compress --mangle --comments '/pagexray/' > dist/pagexray.min.js", | ||
"dist": "npm run browserify && npm run uglify" | ||
}, | ||
@@ -46,11 +50,18 @@ "engines": { | ||
"devDependencies": { | ||
"babel-preset-es2015": "^6.24.1", | ||
"babelify": "^7.3.0", | ||
"bluebird": "^3.3.5", | ||
"chai": "^4.0.1", | ||
"eslint": "^3.5.0", | ||
"eslint-config-airbnb-base": "^11.2.0", | ||
"eslint-plugin-import": "^2.3.0", | ||
"jsdoc": "^3.4.0", | ||
"browserify": "^14.4.0", | ||
"browserify-banner": "^1.0.4", | ||
"chai": "^4.1.0", | ||
"eslint": "^4.2.0", | ||
"eslint-config-prettier": "^2.3.0", | ||
"eslint-plugin-prettier": "^2.1.2", | ||
"jsdoc": "^3.5.3", | ||
"lodash.foreach": "^4.2.0", | ||
"lodash.pluck": "^3.1.2", | ||
"mocha": "^3.0.2" | ||
"mkdirp": "^0.5.1", | ||
"mocha": "^3.0.2", | ||
"prettier": "^1.5.3", | ||
"uglify-es": "^3.0.24" | ||
}, | ||
@@ -60,5 +71,4 @@ "main": "./lib/index.js", | ||
"dependencies": { | ||
"fast-stats": "0.0.3", | ||
"minimist": "1.2.0" | ||
} | ||
} |
123
README.md
@@ -7,3 +7,3 @@ # PageXray | ||
We love the HAR file but it's hard to actually see what the page includes only looking at the file. The PageXray converts a HAR file to a JSON format that is easier to read. We use the format internally in the coach and sitespeed.io. | ||
We love the HAR file but it's hard to actually see what the page includes only looking at the file. The PageXray converts a HAR file to a JSON format that is easier to read. We use the format internally in the coach and sitespeed.io. And with PageXray you can use it standalone in your browser. | ||
@@ -34,3 +34,3 @@ ## What do we collect? | ||
```bash | ||
pagexray -includeAssets /path/to/my.har | ||
pagexray --includeAssets /path/to/my.har | ||
``` | ||
@@ -44,2 +44,9 @@ | ||
``` | ||
## Using in your browser | ||
Include the latest pagexray.min.js (that you find in the relases) on your page. PageXray is exposed as *window.PageXray* | ||
```javascript | ||
const pageXray = window.PageXray.convert(har); | ||
``` | ||
## Output | ||
@@ -52,18 +59,27 @@ All sizes are in bytes. Expires and timeSinceLastModified are in seconds. | ||
"url": "https://www.sitespeed.io/", | ||
"meta": { | ||
"browser": { | ||
"name": "Chrome", | ||
"version": "60.0.3112.78" | ||
}, | ||
"startedDateTime": "2017-08-24T18:26:29.077Z", | ||
"connectivity": "native", | ||
"title": "Sitespeed.io - Welcome to the wonderful world of Web Performance run 1" | ||
}, | ||
"finalUrl": "https://www.sitespeed.io/", | ||
"baseDomain": "www.sitespeed.io", | ||
"documentRedirects": -1, | ||
"documentRedirects": 0, | ||
"redirectChain": [], | ||
"transferSize": 160894, | ||
"contentSize": 221699, | ||
"headerSize": 8027, | ||
"requests": 12, | ||
"transferSize": 98791, | ||
"contentSize": 120776, | ||
"headerSize": 0, | ||
"requests": 10, | ||
"missingCompression": 0, | ||
"httpType": "h1", | ||
"httpVersion": "HTTP/1.1", | ||
"httpType": "h2", | ||
"httpVersion": "HTTP/2.0", | ||
"contentTypes": { | ||
"html": { | ||
"transferSize": 7276, | ||
"contentSize": 24962, | ||
"headerSize": 776, | ||
"transferSize": 8479, | ||
"contentSize": 28279, | ||
"headerSize": 0, | ||
"requests": 1 | ||
@@ -79,11 +95,11 @@ }, | ||
"transferSize": 0, | ||
"contentSize": 43082, | ||
"contentSize": 0, | ||
"headerSize": 0, | ||
"requests": 1 | ||
"requests": 0 | ||
}, | ||
"image": { | ||
"transferSize": 153620, | ||
"contentSize": 153655, | ||
"headerSize": 7253, | ||
"requests": 10 | ||
"transferSize": 87309, | ||
"contentSize": 85979, | ||
"headerSize": 0, | ||
"requests": 8 | ||
}, | ||
@@ -95,2 +111,8 @@ "font": { | ||
"requests": 0 | ||
}, | ||
"favicon": { | ||
"transferSize": 3003, | ||
"contentSize": 6518, | ||
"headerSize": 0, | ||
"requests": 1 | ||
} | ||
@@ -100,3 +122,3 @@ }, | ||
"responseCodes": { | ||
"200": 12 | ||
"200": 10 | ||
}, | ||
@@ -107,39 +129,52 @@ "firstParty": {}, | ||
"www.sitespeed.io": { | ||
"transferSize": 98791, | ||
"contentSize": 120776, | ||
"headerSize": -10, | ||
"requests": 10, | ||
"transferSize": 160896, | ||
"contentSize": 178582, | ||
"headerSize": 8029 | ||
}, | ||
"ssl.google-analytics.com": { | ||
"requests": 2, | ||
"transferSize": -2, | ||
"contentSize": 43117, | ||
"headerSize": -2 | ||
"timings": { | ||
"blocked": 169, | ||
"dns": 0, | ||
"connect": 0, | ||
"send": 6, | ||
"wait": 3624, | ||
"receive": 104 | ||
} | ||
} | ||
}, | ||
"expireStats": { | ||
"min": 0, | ||
"p10": 600, | ||
"min": 600, | ||
"median": 31536000, | ||
"p90": 31536000, | ||
"p99": 31536000, | ||
"max": 31536000 | ||
"max": 31536000, | ||
"total": 283824600, | ||
"values": 10 | ||
}, | ||
"lastModifiedStats": { | ||
"min": 3121029, | ||
"p10": 8475293, | ||
"median": 8475313, | ||
"p90": 8475339, | ||
"p99": 578982140, | ||
"max": 578982140 | ||
"min": 733347, | ||
"median": 733444, | ||
"max": 733480, | ||
"total": 7334359, | ||
"values": 10 | ||
}, | ||
"cookieStats": { | ||
"min": 0, | ||
"p10": 0, | ||
"median": 0, | ||
"p90": 0, | ||
"p99": 0, | ||
"max": 0 | ||
"max": 0, | ||
"total": 0, | ||
"values": 10 | ||
}, | ||
"totalDomains": 2 | ||
"totalDomains": 1, | ||
"visualMetrics": { | ||
"FirstVisualChange": 617, | ||
"SpeedIndex": 625, | ||
"VisualComplete85": 617, | ||
"LastVisualChange": 1033, | ||
"VisualProgress": { | ||
"0": 0, | ||
"617": 98, | ||
"633": 98, | ||
"667": 98, | ||
"850": 98, | ||
"1033": 100 | ||
} | ||
} | ||
} | ||
@@ -146,0 +181,0 @@ ] |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
30888
1
14
605
0
179
16
- Removedfast-stats@0.0.3
- Removedfast-stats@0.0.3(transitive)