Skip to content

Instantly share code, notes, and snippets.

@brianloveswords
Last active April 26, 2021 04:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brianloveswords/bb9ded318ba1e8d8ffa1d23817408b6c to your computer and use it in GitHub Desktop.
Save brianloveswords/bb9ded318ba1e8d8ffa1d23817408b6c to your computer and use it in GitHub Desktop.
BigQuery Cost Estimator

BigQuery Cost Estimator

Get an estimate of how much a query is going to cost before you run it.

Instructions

  1. Create a new bookmark with the contents of 2-bookmarklet as the URL.
  2. Whenever you open a new BQ session, click the bookmarklet to attach the observer. You only have to do it once per session.
javascript:!function(){const t=t=>1024*t,n=t=>t,r=t=>t/1024,e=t=>t/1024/1024,i=t=>t/1024/1024/1024,o=t=>t/1024/1024/1024/1024,s=s=>(s=>{if(s.endsWith(" B"))return o;if(s.endsWith(" KB"))return i;if(s.endsWith(" MB"))return e;if(s.endsWith(" GB"))return r;if(s.endsWith(" TB"))return n;if(s.endsWith(" PB"))return t;throw new Error(`could not determine units in "${s}"`)})(s)(parseFloat(s)),u=t=>t.firstChild&&3===t.firstChild.nodeType&&t.firstChild.textContent.includes("query will process"),d=t=>{const n=t.data;if((t=>t.includes("cost"))(n))return;const r=(t=>{const n=t.match(/\d+(\.\d+)? (B|KB|MB|GB|TB|PB)/);if(!n)throw new Error(`could not find a unit string in "${t}"`);return n[0]})(n);if(!r)return;const e=(t=>`$${(5*t).toFixed(2)}`)(s(r));t.data=((t,n)=>t.replace("when run",`and cost ${n} when run`))(n,e)},c=()=>{const t=((t,n)=>{const r=(t,n)=>{if(n(t))return t;if(!t.children)return null;for(const e of t.children){const t=r(e,n);if(t)return t}return null};return r(t,n)})(document.body,u);if(!t)return void setTimeout(c,1e3);const n=t.firstChild;if(!n)return void setTimeout(c,1e3);new MutationObserver(t=>{const n=t[0].target;d(n)}).observe(n,{characterData:!0,childList:!1,subtree:!1}),d(n)};c()}();
(function() {
const treeFilter = (node, predicate) => {
const walker = (node, predicate) => {
if (predicate(node)) {
return node;
}
if (!node.children) {
return null;
}
for (const child of node.children) {
const found = walker(child, predicate);
if (found) {
return found;
}
}
return null;
};
return walker(node, predicate);
};
const petabytesToTB = u => u * 1024;
const terabytesToTB = u => u;
const gigabytesToTB = u => u / 1024;
const megabytesToTB = u => u / 1024 / 1024;
const kilobytesToTB = u => u / 1024 / 1024 / 1024;
const bytesToTB = u => u / 1024 / 1024 / 1024 / 1024;
const extractUnitString = str => {
const match = str.match(/\d+(\.\d+)? (B|KB|MB|GB|TB|PB)/);
if (!match) {
throw new Error(`could not find a unit string in "${str}"`);
}
return match[0];
};
const conversionFn = unitString => {
if (unitString.endsWith(` B`)) return bytesToTB;
if (unitString.endsWith(` KB`)) return kilobytesToTB;
if (unitString.endsWith(` MB`)) return megabytesToTB;
if (unitString.endsWith(` GB`)) return gigabytesToTB;
if (unitString.endsWith(` TB`)) return terabytesToTB;
if (unitString.endsWith(` PB`)) return petabytesToTB;
throw new Error(`could not determine units in "${unitString}"`);
};
const unitStringToTB = unitString => conversionFn(unitString)(parseFloat(unitString));
const tbToMoney = tb => `$${(tb * 5.0).toFixed(2)}`;
const isQueryValidatorNode = e =>
e.firstChild &&
e.firstChild.nodeType === 3 &&
e.firstChild.textContent.includes("query will process");
const appendCostToQueryInfo = (queryInfo, cost) =>
queryInfo.replace("when run", `and cost ${cost} when run`);
const queryInfoContainsCost = queryInfo => queryInfo.includes("cost");
const addCostToQueryInfoNode = textNode => {
const queryInfo = textNode.data;
if (queryInfoContainsCost(queryInfo)) {
return;
}
const unitString = extractUnitString(queryInfo);
if (!unitString) {
return;
}
const cost = tbToMoney(unitStringToTB(unitString));
textNode.data = appendCostToQueryInfo(queryInfo, cost);
};
const addMutationObserver = () => {
const validatorNode = treeFilter(document.body, isQueryValidatorNode);
if (!validatorNode) {
setTimeout(addMutationObserver, 1000);
return;
}
const textNode = validatorNode.firstChild;
if (!textNode) {
setTimeout(addMutationObserver, 1000);
return;
}
const config = { characterData: true, childList: false, subtree: false };
const callback = mutationList => {
const textNode = mutationList[0].target;
addCostToQueryInfoNode(textNode);
};
const observer = new MutationObserver(callback);
observer.observe(textNode, config);
addCostToQueryInfoNode(textNode);
};
addMutationObserver();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment