diff --git a/.buildconfig.yml b/.buildconfig.yml index 7d32595947..038a32b5d3 100644 --- a/.buildconfig.yml +++ b/.buildconfig.yml @@ -1,4 +1,4 @@ -libraryVersion: 37.0.0 +libraryVersion: 38.0.0 groupId: org.mozilla.telemetry projects: glean: diff --git a/.circleci/config.yml b/.circleci/config.yml index 4b8303610b..32a2861e25 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -528,8 +528,7 @@ jobs: - run: name: Install new carthage command: | - time brew update - time brew upgrade carthage + brew upgrade carthage - run: name: Set Ruby Version command: echo 'chruby ruby-2.7.2' >> ~/.bash_profile @@ -636,8 +635,7 @@ jobs: - run: name: Install new carthage command: | - time brew update - time brew upgrade carthage + brew upgrade carthage - install-rustup - setup-rust-toolchain - run: diff --git a/.dictionary b/.dictionary index f0b39337bd..ee3ee099df 100644 --- a/.dictionary +++ b/.dictionary @@ -1,10 +1,11 @@ -personal_ws-1.1 en 220 utf-8 +personal_ws-1.1 en 221 utf-8 AAR AARs ABI API's APIs APK +AppServices BUGFIX Bool Booleans diff --git a/CHANGELOG.md b/CHANGELOG.md index 167dc001f0..390aee71b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Unreleased changes -[Full changelog](https://github.com/mozilla/glean/compare/v37.0.0...main) +[Full changelog](https://github.com/mozilla/glean/compare/v38.0.0...main) + +# v38.0.0 (2021-05-12) + +[Full changelog](https://github.com/mozilla/glean/compare/v37.0.0...v38.0.0) + +* General + * Update documentation to recommend using Glean Dictionary instead of metrics.md ([#1604](https://github.com/mozilla/glean/pull/1604)) +* Rust + * **Breaking Change**: Don't return a result from `submit_ping`. The boolean return value indicates whether a ping was submitted ([#1613](https://github.com/mozilla/glean/pull/1613)) + * **Breaking Change**: Glean now schedules "metrics" pings, accepting a new Configuration parameter. ([#1599](https://github.com/mozilla/glean/pull/1599)) + * Dispatch setting the source tag to avoid a potential crash ([#1614](https://github.com/mozilla/glean/pull/1614)) + * Testing mode will wait for init & upload tasks to finish ([#1628](https://github.com/mozilla/glean/pull/1628)) +* Android + * Set required fields for `client_info` before optional ones ([#1633](https://github.com/mozilla/glean/pull/1633)) + * Provide forward-compatibility with Gradle 6.8 ([#1616](https://github.com/mozilla/glean/pull/1633)) # v37.0.0 (2021-04-30) diff --git a/Cargo.lock b/Cargo.lock index 4f77033fae..7afe564bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,7 +221,7 @@ dependencies = [ [[package]] name = "glean" -version = "37.0.0" +version = "38.0.0" dependencies = [ "chrono", "crossbeam-channel", @@ -243,7 +243,7 @@ dependencies = [ [[package]] name = "glean-core" -version = "37.0.0" +version = "38.0.0" dependencies = [ "bincode", "chrono", @@ -265,7 +265,7 @@ dependencies = [ [[package]] name = "glean-ffi" -version = "37.0.0" +version = "38.0.0" dependencies = [ "android_logger", "env_logger", diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index f95d15c665..58e1ee06bf 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -3248,9 +3248,9 @@ THE SOFTWARE. The following text applies to code linked from these dependencies: -* [glean 37.0.0]( https://github.com/mozilla/glean ) -* [glean-core 37.0.0]( https://github.com/mozilla/glean ) -* [glean-ffi 37.0.0]( https://github.com/mozilla/glean ) +* [glean 38.0.0]( https://github.com/mozilla/glean ) +* [glean-core 38.0.0]( https://github.com/mozilla/glean ) +* [glean-ffi 38.0.0]( https://github.com/mozilla/glean ) * [zeitstempel 0.1.1]( https://github.com/badboy/zeitstempel ) diff --git a/build.gradle b/build.gradle index 62a1994bb1..d9bdf37614 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { mockito: '2.28.2', // This is different than a-c, but we're fine, it's only tests. mockwebserver: '4.9.1', // This is different than a-c, but we're fine, it's only tests. kotlin: '1.4.10', - robolectric: '4.2.1', // This is different than a-c, but we're fine, it's only tests. + robolectric: '4.5.1', // This is different than a-c, but we're fine, it's only tests. rust_android_plugin: '0.8.3', // Android X dependencies diff --git a/docs/shared/tabs.js b/docs/shared/tabs.js index 6910502b7a..b16076e6b6 100644 --- a/docs/shared/tabs.js +++ b/docs/shared/tabs.js @@ -82,7 +82,12 @@ function switchAllTabs(language) { button.classList.add("tablinks"); if (!tabcontent.innerHTML) { button.classList.add("disabled"); - button.title = `${tabcontent.dataset.lang} does not provide this API`; + if (!tabcontent.dataset.bug) { + button.title = `${tabcontent.dataset.lang} does not provide this API`; + } else { + button.title = + `${tabcontent.dataset.lang} does not provide this API yet. \nFollow https://bugzilla.mozilla.org/show_bug.cgi?id=${tabcontent.dataset.bug} for updates.`; + } } else { button.onclick = onClickTab; } diff --git a/docs/user/SUMMARY.md b/docs/user/SUMMARY.md index 77dc5c912c..f6cfb0dbe7 100644 --- a/docs/user/SUMMARY.md +++ b/docs/user/SUMMARY.md @@ -46,6 +46,7 @@ # Language Bindings Information +- [Overview](language-bindings/index.md) - [Android](language-bindings/android/index.md) - [Android build configuration options](language-bindings/android/android-build-configuration-options.md) - [Android offline builds](language-bindings/android/android-offline-builds.md) diff --git a/docs/user/chart.min.js b/docs/user/chart.min.js index 0fb1ce9bd3..a87f61443e 100644 --- a/docs/user/chart.min.js +++ b/docs/user/chart.min.js @@ -1,7 +1,7 @@ /*! - * Chart.js v2.9.3 + * Chart.js v2.9.4 * https://www.chartjs.org - * (c) 2019 Chart.js Contributors + * (c) 2020 Chart.js Contributors * Released under the MIT License */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(function(){try{return require("moment")}catch(t){}}()):"function"==typeof define&&define.amd?define(["require"],(function(t){return e(function(){try{return t("moment")}catch(t){}}())})):(t=t||self).Chart=e(t.moment)}(this,(function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var e={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},n=function(t,e){return t(e={exports:{}},e.exports),e.exports}((function(t){var n={};for(var i in e)e.hasOwnProperty(i)&&(n[e[i]]=i);var a=t.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var r in a)if(a.hasOwnProperty(r)){if(!("channels"in a[r]))throw new Error("missing channels property: "+r);if(!("labels"in a[r]))throw new Error("missing channel labels property: "+r);if(a[r].labels.length!==a[r].channels)throw new Error("channel and label counts mismatch: "+r);var o=a[r].channels,s=a[r].labels;delete a[r].channels,delete a[r].labels,Object.defineProperty(a[r],"channels",{value:o}),Object.defineProperty(a[r],"labels",{value:s})}a.rgb.hsl=function(t){var e,n,i=t[0]/255,a=t[1]/255,r=t[2]/255,o=Math.min(i,a,r),s=Math.max(i,a,r),l=s-o;return s===o?e=0:i===s?e=(a-r)/l:a===s?e=2+(r-i)/l:r===s&&(e=4+(i-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),n=(o+s)/2,[e,100*(s===o?0:n<=.5?l/(s+o):l/(2-s-o)),100*n]},a.rgb.hsv=function(t){var e,n,i,a,r,o=t[0]/255,s=t[1]/255,l=t[2]/255,u=Math.max(o,s,l),d=u-Math.min(o,s,l),h=function(t){return(u-t)/6/d+.5};return 0===d?a=r=0:(r=d/u,e=h(o),n=h(s),i=h(l),o===u?a=i-n:s===u?a=1/3+e-i:l===u&&(a=2/3+n-e),a<0?a+=1:a>1&&(a-=1)),[360*a,100*r,100*u]},a.rgb.hwb=function(t){var e=t[0],n=t[1],i=t[2];return[a.rgb.hsl(t)[0],100*(1/255*Math.min(e,Math.min(n,i))),100*(i=1-1/255*Math.max(e,Math.max(n,i)))]},a.rgb.cmyk=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255;return[100*((1-n-(e=Math.min(1-n,1-i,1-a)))/(1-e)||0),100*((1-i-e)/(1-e)||0),100*((1-a-e)/(1-e)||0),100*e]},a.rgb.keyword=function(t){var i=n[t];if(i)return i;var a,r,o,s=1/0;for(var l in e)if(e.hasOwnProperty(l)){var u=e[l],d=(r=t,o=u,Math.pow(r[0]-o[0],2)+Math.pow(r[1]-o[1],2)+Math.pow(r[2]-o[2],2));d.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]},a.rgb.lab=function(t){var e=a.rgb.xyz(t),n=e[0],i=e[1],r=e[2];return i/=100,r/=108.883,n=(n/=95.047)>.008856?Math.pow(n,1/3):7.787*n+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(n-i),200*(i-(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116))]},a.hsl.rgb=function(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0===s)return[r=255*l,r,r];e=2*l-(n=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(i=o+1/3*-(u-1))<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a},a.hsl.hsv=function(t){var e=t[0],n=t[1]/100,i=t[2]/100,a=n,r=Math.max(i,.01);return n*=(i*=2)<=1?i:2-i,a*=r<=1?r:2-r,[e,100*(0===i?2*a/(r+a):2*n/(i+n)),100*((i+n)/2)]},a.hsv.rgb=function(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r));switch(i*=255,a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}},a.hsv.hsl=function(t){var e,n,i,a=t[0],r=t[1]/100,o=t[2]/100,s=Math.max(o,.01);return i=(2-r)*o,n=r*s,[a,100*(n=(n/=(e=(2-r)*s)<=1?e:2-e)||0),100*(i/=2)]},a.hwb.rgb=function(t){var e,n,i,a,r,o,s,l=t[0]/360,u=t[1]/100,d=t[2]/100,h=u+d;switch(h>1&&(u/=h,d/=h),i=6*l-(e=Math.floor(6*l)),0!=(1&e)&&(i=1-i),a=u+i*((n=1-d)-u),e){default:case 6:case 0:r=n,o=a,s=u;break;case 1:r=a,o=n,s=u;break;case 2:r=u,o=n,s=a;break;case 3:r=u,o=a,s=n;break;case 4:r=a,o=u,s=n;break;case 5:r=n,o=u,s=a}return[255*r,255*o,255*s]},a.cmyk.rgb=function(t){var e=t[0]/100,n=t[1]/100,i=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a))]},a.xyz.rgb=function(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return n=-.9689*a+1.8758*r+.0415*o,i=.0557*a+-.204*r+1.057*o,e=(e=3.2406*a+-1.5372*r+-.4986*o)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:12.92*i,[255*(e=Math.min(Math.max(0,e),1)),255*(n=Math.min(Math.max(0,n),1)),255*(i=Math.min(Math.max(0,i),1))]},a.xyz.lab=function(t){var e=t[0],n=t[1],i=t[2];return n/=100,i/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(e-n),200*(n-(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116))]},a.lab.xyz=function(t){var e,n,i,a=t[0];e=t[1]/500+(n=(a+16)/116),i=n-t[2]/200;var r=Math.pow(n,3),o=Math.pow(e,3),s=Math.pow(i,3);return n=r>.008856?r:(n-16/116)/7.787,e=o>.008856?o:(e-16/116)/7.787,i=s>.008856?s:(i-16/116)/7.787,[e*=95.047,n*=100,i*=108.883]},a.lab.lch=function(t){var e,n=t[0],i=t[1],a=t[2];return(e=360*Math.atan2(a,i)/2/Math.PI)<0&&(e+=360),[n,Math.sqrt(i*i+a*a),e]},a.lch.lab=function(t){var e,n=t[0],i=t[1];return e=t[2]/360*2*Math.PI,[n,i*Math.cos(e),i*Math.sin(e)]},a.rgb.ansi16=function(t){var e=t[0],n=t[1],i=t[2],r=1 in arguments?arguments[1]:a.rgb.hsv(t)[2];if(0===(r=Math.round(r/50)))return 30;var o=30+(Math.round(i/255)<<2|Math.round(n/255)<<1|Math.round(e/255));return 2===r&&(o+=60),o},a.hsv.ansi16=function(t){return a.rgb.ansi16(a.hsv.rgb(t),t[2])},a.rgb.ansi256=function(t){var e=t[0],n=t[1],i=t[2];return e===n&&n===i?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(n/255*5)+Math.round(i/255*5)},a.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var n=.5*(1+~~(t>50));return[(1&e)*n*255,(e>>1&1)*n*255,(e>>2&1)*n*255]},a.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var n;return t-=16,[Math.floor(t/36)/5*255,Math.floor((n=t%36)/6)/5*255,n%6/5*255]},a.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},a.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var n=e[0];3===e[0].length&&(n=n.split("").map((function(t){return t+t})).join(""));var i=parseInt(n,16);return[i>>16&255,i>>8&255,255&i]},a.rgb.hcg=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255,r=Math.max(Math.max(n,i),a),o=Math.min(Math.min(n,i),a),s=r-o;return e=s<=0?0:r===n?(i-a)/s%6:r===i?2+(a-n)/s:4+(n-i)/s+4,e/=6,[360*(e%=1),100*s,100*(s<1?o/(1-s):0)]},a.hsl.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=1,a=0;return(i=n<.5?2*e*n:2*e*(1-n))<1&&(a=(n-.5*i)/(1-i)),[t[0],100*i,100*a]},a.hsv.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=e*n,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.hcg.rgb=function(t){var e=t[0]/360,n=t[1]/100,i=t[2]/100;if(0===n)return[255*i,255*i,255*i];var a,r=[0,0,0],o=e%1*6,s=o%1,l=1-s;switch(Math.floor(o)){case 0:r[0]=1,r[1]=s,r[2]=0;break;case 1:r[0]=l,r[1]=1,r[2]=0;break;case 2:r[0]=0,r[1]=1,r[2]=s;break;case 3:r[0]=0,r[1]=l,r[2]=1;break;case 4:r[0]=s,r[1]=0,r[2]=1;break;default:r[0]=1,r[1]=0,r[2]=l}return a=(1-n)*i,[255*(n*r[0]+a),255*(n*r[1]+a),255*(n*r[2]+a)]},a.hcg.hsv=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e),i=0;return n>0&&(i=e/n),[t[0],100*i,100*n]},a.hcg.hsl=function(t){var e=t[1]/100,n=t[2]/100*(1-e)+.5*e,i=0;return n>0&&n<.5?i=e/(2*n):n>=.5&&n<1&&(i=e/(2*(1-n))),[t[0],100*i,100*n]},a.hcg.hwb=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e);return[t[0],100*(n-e),100*(1-n)]},a.hwb.hcg=function(t){var e=t[1]/100,n=1-t[2]/100,i=n-e,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},a.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},a.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},a.gray.hsl=a.gray.hsv=function(t){return[0,0,t[0]]},a.gray.hwb=function(t){return[0,100,t[0]]},a.gray.cmyk=function(t){return[0,0,0,t[0]]},a.gray.lab=function(t){return[t[0],0,0]},a.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),n=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(n.length)+n},a.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}}));n.rgb,n.hsl,n.hsv,n.hwb,n.cmyk,n.xyz,n.lab,n.lch,n.hex,n.keyword,n.ansi16,n.ansi256,n.hcg,n.apple,n.gray;function i(t){var e=function(){for(var t={},e=Object.keys(n),i=e.length,a=0;a1&&(e=Array.prototype.slice.call(arguments));var n=t(e);if("object"==typeof n)for(var i=n.length,a=0;a1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}(i)}))}));var s=o,l={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},u={getRgba:d,getHsla:h,getRgb:function(t){var e=d(t);return e&&e.slice(0,3)},getHsl:function(t){var e=h(t);return e&&e.slice(0,3)},getHwb:c,getAlpha:function(t){var e=d(t);if(e)return e[3];if(e=h(t))return e[3];if(e=c(t))return e[3]},hexString:function(t,e){e=void 0!==e&&3===t.length?e:t[3];return"#"+v(t[0])+v(t[1])+v(t[2])+(e>=0&&e<1?v(Math.round(255*e)):"")},rgbString:function(t,e){if(e<1||t[3]&&t[3]<1)return f(t,e);return"rgb("+t[0]+", "+t[1]+", "+t[2]+")"},rgbaString:f,percentString:function(t,e){if(e<1||t[3]&&t[3]<1)return g(t,e);var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+n+"%, "+i+"%, "+a+"%)"},percentaString:g,hslString:function(t,e){if(e<1||t[3]&&t[3]<1)return p(t,e);return"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"},hslaString:p,hwbString:function(t,e){void 0===e&&(e=void 0!==t[3]?t[3]:1);return"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"},keyword:function(t){return b[t.slice(0,3)]}};function d(t){if(t){var e=[0,0,0],n=1,i=t.match(/^#([a-fA-F0-9]{3,4})$/i),a="";if(i){a=(i=i[1])[3];for(var r=0;rn?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=t,i=void 0===e?.5:e,a=2*i-1,r=this.alpha()-n.alpha(),o=((a*r==-1?a:(a+r)/(1+a*r))+1)/2,s=1-o;return this.rgb(o*this.red()+s*n.red(),o*this.green()+s*n.green(),o*this.blue()+s*n.blue()).alpha(this.alpha()*i+n.alpha()*(1-i))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new y,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},y.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},y.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},y.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i=0;a--)e.call(n,t[a],a);else for(a=0;a=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-S.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*S.easeInBounce(2*t):.5*S.easeOutBounce(2*t-1)+.5}},C={effects:S};M.easingEffects=S;var P=Math.PI,A=P/180,D=2*P,T=P/2,I=P/4,F=2*P/3,L={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,r){if(r){var o=Math.min(r,a/2,i/2),s=e+o,l=n+o,u=e+i-o,d=n+a-o;t.moveTo(e,l),se.left-1e-6&&t.xe.top-1e-6&&t.y0&&this.requestAnimationFrame()},advance:function(){for(var t,e,n,i,a=this.animations,r=0;r=n?(V.callback(t.onAnimationComplete,[t],e),e.animating=!1,a.splice(r,1)):++r}},J=V.options.resolve,Q=["push","pop","shift","splice","unshift"];function tt(t,e){var n=t._chartjs;if(n){var i=n.listeners,a=i.indexOf(e);-1!==a&&i.splice(a,1),i.length>0||(Q.forEach((function(e){delete t[e]})),delete t._chartjs)}}var et=function(t,e){this.initialize(t,e)};V.extend(et.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements(),n._type=n.getMeta().type},updateIndex:function(t){this.index=t},linkScales:function(){var t=this.getMeta(),e=this.chart,n=e.scales,i=this.getDataset(),a=e.options.scales;null!==t.xAxisID&&t.xAxisID in n&&!i.xAxisID||(t.xAxisID=i.xAxisID||a.xAxes[0].id),null!==t.yAxisID&&t.yAxisID in n&&!i.yAxisID||(t.yAxisID=i.yAxisID||a.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&tt(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,n=this.getMeta(),i=this.getDataset().data||[],a=n.data;for(t=0,e=i.length;tn&&this.insertElements(n,i-n)},insertElements:function(t,e){for(var n=0;na?(r=a/e.innerRadius,t.arc(o,s,e.innerRadius-a,i+r,n-r,!0)):t.arc(o,s,a,i+Math.PI/2,n-Math.PI/2),t.closePath(),t.clip()}function rt(t,e,n){var i="inner"===e.borderAlign;i?(t.lineWidth=2*e.borderWidth,t.lineJoin="round"):(t.lineWidth=e.borderWidth,t.lineJoin="bevel"),n.fullCircles&&function(t,e,n,i){var a,r=n.endAngle;for(i&&(n.endAngle=n.startAngle+it,at(t,n),n.endAngle=r,n.endAngle===n.startAngle&&n.fullCircles&&(n.endAngle+=it,n.fullCircles--)),t.beginPath(),t.arc(n.x,n.y,n.innerRadius,n.startAngle+it,n.startAngle,!0),a=0;as;)a-=it;for(;a=o&&a<=s,u=r>=n.innerRadius&&r<=n.outerRadius;return l&&u}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t,e=this._chart.ctx,n=this._view,i="inner"===n.borderAlign?.33:0,a={x:n.x,y:n.y,innerRadius:n.innerRadius,outerRadius:Math.max(n.outerRadius-i,0),pixelMargin:i,startAngle:n.startAngle,endAngle:n.endAngle,fullCircles:Math.floor(n.circumference/it)};if(e.save(),e.fillStyle=n.backgroundColor,e.strokeStyle=n.borderColor,a.fullCircles){for(a.endAngle=a.startAngle+it,e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),t=0;tt.x&&(e=vt(e,"left","right")):t.basen?n:i,r:l.right||a<0?0:a>e?e:a,b:l.bottom||r<0?0:r>n?n:r,l:l.left||o<0?0:o>e?e:o}}function xt(t,e,n){var i=null===e,a=null===n,r=!(!t||i&&a)&&mt(t);return r&&(i||e>=r.left&&e<=r.right)&&(a||n>=r.top&&n<=r.bottom)}z._set("global",{elements:{rectangle:{backgroundColor:gt,borderColor:gt,borderSkipped:"bottom",borderWidth:0}}});var yt=X.extend({_type:"rectangle",draw:function(){var t=this._chart.ctx,e=this._view,n=function(t){var e=mt(t),n=e.right-e.left,i=e.bottom-e.top,a=bt(t,n/2,i/2);return{outer:{x:e.left,y:e.top,w:n,h:i},inner:{x:e.left+a.l,y:e.top+a.t,w:n-a.l-a.r,h:i-a.t-a.b}}}(e),i=n.outer,a=n.inner;t.fillStyle=e.backgroundColor,t.fillRect(i.x,i.y,i.w,i.h),i.w===a.w&&i.h===a.h||(t.save(),t.beginPath(),t.rect(i.x,i.y,i.w,i.h),t.clip(),t.fillStyle=e.borderColor,t.rect(a.x,a.y,a.w,a.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return xt(this._view,t,e)},inLabelRange:function(t,e){var n=this._view;return pt(n)?xt(n,t,null):xt(n,null,e)},inXRange:function(t){return xt(this._view,t,null)},inYRange:function(t){return xt(this._view,null,t)},getCenterPoint:function(){var t,e,n=this._view;return pt(n)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return pt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),_t={},kt=ot,wt=ut,Mt=ft,St=yt;_t.Arc=kt,_t.Line=wt,_t.Point=Mt,_t.Rectangle=St;var Ct=V._deprecated,Pt=V.valueOrDefault;function At(t,e,n){var i,a,r=n.barThickness,o=e.stackCount,s=e.pixels[t],l=V.isNullOrUndef(r)?function(t,e){var n,i,a,r,o=t._length;for(a=1,r=e.length;a0?Math.min(o,Math.abs(i-n)):o,n=i;return o}(e.scale,e.pixels):-1;return V.isNullOrUndef(r)?(i=l*n.categoryPercentage,a=n.barPercentage):(i=r*o,a=1),{chunk:i/o,ratio:a,start:s-i/2}}z._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),z._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var Dt=nt.extend({dataElementType:_t.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var t,e,n=this;nt.prototype.initialize.apply(n,arguments),(t=n.getMeta()).stack=n.getDataset().stack,t.bar=!0,e=n._getIndexScale().options,Ct("bar chart",e.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),Ct("bar chart",e.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),Ct("bar chart",e.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),Ct("bar chart",n._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),Ct("bar chart",e.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(t){var e,n,i=this.getMeta().data;for(this._ruler=this.getRuler(),e=0,n=i.length;e=0&&p.min>=0?p.min:p.max,y=void 0===p.start?p.end:p.max>=0&&p.min>=0?p.max-p.min:p.min-p.max,_=g.length;if(v||void 0===v&&void 0!==b)for(i=0;i<_&&(a=g[i]).index!==t;++i)a.stack===b&&(r=void 0===(u=h._parseValue(f[a.index].data[e])).start?u.end:u.min>=0&&u.max>=0?u.max:u.min,(p.min<0&&r<0||p.max>=0&&r>0)&&(x+=r));return o=h.getPixelForValue(x),l=(s=h.getPixelForValue(x+y))-o,void 0!==m&&Math.abs(l)=0&&!c||y<0&&c?o-m:o+m),{size:l,base:o,head:s,center:s+l/2}},calculateBarIndexPixels:function(t,e,n,i){var a="flex"===i.barThickness?function(t,e,n){var i,a=e.pixels,r=a[t],o=t>0?a[t-1]:null,s=t=Ot?-Rt:b<-Ot?Rt:0)+m,y=Math.cos(b),_=Math.sin(b),k=Math.cos(x),w=Math.sin(x),M=b<=0&&x>=0||x>=Rt,S=b<=zt&&x>=zt||x>=Rt+zt,C=b<=-zt&&x>=-zt||x>=Ot+zt,P=b===-Ot||x>=Ot?-1:Math.min(y,y*p,k,k*p),A=C?-1:Math.min(_,_*p,w,w*p),D=M?1:Math.max(y,y*p,k,k*p),T=S?1:Math.max(_,_*p,w,w*p);u=(D-P)/2,d=(T-A)/2,h=-(D+P)/2,c=-(T+A)/2}for(i=0,a=g.length;i0&&!isNaN(t)?Rt*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,n,i,a,r,o,s,l,u=0,d=this.chart;if(!t)for(e=0,n=d.data.datasets.length;e(u=s>u?s:u)?l:u);return u},setHoverStyle:function(t){var e=t._model,n=t._options,i=V.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=Lt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Lt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Lt(n.hoverBorderWidth,n.borderWidth)},_getRingWeightOffset:function(t){for(var e=0,n=0;n0&&Vt(l[t-1]._model,s)&&(n.controlPointPreviousX=u(n.controlPointPreviousX,s.left,s.right),n.controlPointPreviousY=u(n.controlPointPreviousY,s.top,s.bottom)),t0&&(r=t.getDatasetMeta(r[0]._datasetIndex).data),r},"x-axis":function(t,e){return ie(t,e,{intersect:!1})},point:function(t,e){return te(t,Jt(e,t))},nearest:function(t,e,n){var i=Jt(e,t);n.axis=n.axis||"xy";var a=ne(n.axis);return ee(t,i,n.intersect,a)},x:function(t,e,n){var i=Jt(e,t),a=[],r=!1;return Qt(t,(function(t){t.inXRange(i.x)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a},y:function(t,e,n){var i=Jt(e,t),a=[],r=!1;return Qt(t,(function(t){t.inYRange(i.y)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a}}},re=V.extend;function oe(t,e){return V.where(t,(function(t){return t.pos===e}))}function se(t,e){return t.sort((function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i.index-a.index:i.weight-a.weight}))}function le(t,e,n,i){return Math.max(t[n],e[n])+Math.max(t[i],e[i])}function ue(t,e,n){var i,a,r=n.box,o=t.maxPadding;if(n.size&&(t[n.pos]-=n.size),n.size=n.horizontal?r.height:r.width,t[n.pos]+=n.size,r.getPadding){var s=r.getPadding();o.top=Math.max(o.top,s.top),o.left=Math.max(o.left,s.left),o.bottom=Math.max(o.bottom,s.bottom),o.right=Math.max(o.right,s.right)}if(i=e.outerWidth-le(o,t,"left","right"),a=e.outerHeight-le(o,t,"top","bottom"),i!==t.w||a!==t.h)return t.w=i,t.h=a,n.horizontal?i!==t.w:a!==t.h}function de(t,e){var n=e.maxPadding;function i(t){var i={left:0,top:0,right:0,bottom:0};return t.forEach((function(t){i[t]=Math.max(e[t],n[t])})),i}return i(t?["left","right"]:["top","bottom"])}function he(t,e,n){var i,a,r,o,s,l,u=[];for(i=0,a=t.length;idiv{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}"}))&&fe.default||fe,me="$chartjs",ve="chartjs-size-monitor",be="chartjs-render-monitor",xe="chartjs-render-animation",ye=["animationstart","webkitAnimationStart"],_e={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function ke(t,e){var n=V.getStyle(t,e),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?Number(i[1]):void 0}var we=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};function Me(t,e,n){t.addEventListener(e,n,we)}function Se(t,e,n){t.removeEventListener(e,n,we)}function Ce(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function Pe(t){var e=document.createElement("div");return e.className=t||"",e}function Ae(t,e,n){var i,a,r,o,s=t[me]||(t[me]={}),l=s.resizer=function(t){var e=Pe(ve),n=Pe(ve+"-expand"),i=Pe(ve+"-shrink");n.appendChild(Pe()),i.appendChild(Pe()),e.appendChild(n),e.appendChild(i),e._reset=function(){n.scrollLeft=1e6,n.scrollTop=1e6,i.scrollLeft=1e6,i.scrollTop=1e6};var a=function(){e._reset(),t()};return Me(n,"scroll",a.bind(n,"expand")),Me(i,"scroll",a.bind(i,"shrink")),e}((i=function(){if(s.resizer){var i=n.options.maintainAspectRatio&&t.parentNode,a=i?i.clientWidth:0;e(Ce("resize",n)),i&&i.clientWidth0){var r=t[0];r.label?n=r.label:r.xLabel?n=r.xLabel:a>0&&r.index-1?t.split("\n"):t}function We(t){var e=z.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,rtl:t.rtl,textDirection:t.textDirection,bodyFontColor:t.bodyFontColor,_bodyFontFamily:Re(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:Re(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:Re(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:Re(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:Re(t.titleFontStyle,e.defaultFontStyle),titleFontSize:Re(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:Re(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:Re(t.footerFontStyle,e.defaultFontStyle),footerFontSize:Re(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function Ve(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function He(t){return Be([],Ee(t))}var je=X.extend({initialize:function(){this._model=We(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options,n=e.callbacks,i=n.beforeTitle.apply(t,arguments),a=n.title.apply(t,arguments),r=n.afterTitle.apply(t,arguments),o=[];return o=Be(o,Ee(i)),o=Be(o,Ee(a)),o=Be(o,Ee(r))},getBeforeBody:function(){return He(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var n=this,i=n._options.callbacks,a=[];return V.each(t,(function(t){var r={before:[],lines:[],after:[]};Be(r.before,Ee(i.beforeLabel.call(n,t,e))),Be(r.lines,i.label.call(n,t,e)),Be(r.after,Ee(i.afterLabel.call(n,t,e))),a.push(r)})),a},getAfterBody:function(){return He(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this,e=t._options.callbacks,n=e.beforeFooter.apply(t,arguments),i=e.footer.apply(t,arguments),a=e.afterFooter.apply(t,arguments),r=[];return r=Be(r,Ee(n)),r=Be(r,Ee(i)),r=Be(r,Ee(a))},update:function(t){var e,n,i,a,r,o,s,l,u,d,h=this,c=h._options,f=h._model,g=h._model=We(c),p=h._active,m=h._data,v={xAlign:f.xAlign,yAlign:f.yAlign},b={x:f.x,y:f.y},x={width:f.width,height:f.height},y={x:f.caretX,y:f.caretY};if(p.length){g.opacity=1;var _=[],k=[];y=Ne[c.position].call(h,p,h._eventPosition);var w=[];for(e=0,n=p.length;ei.width&&(a=i.width-e.width),a<0&&(a=0)),"top"===d?r+=h:r-="bottom"===d?e.height+h:e.height/2,"center"===d?"left"===u?a+=h:"right"===u&&(a-=h):"left"===u?a-=c:"right"===u&&(a+=c),{x:a,y:r}}(g,x,v=function(t,e){var n,i,a,r,o,s=t._model,l=t._chart,u=t._chart.chartArea,d="center",h="center";s.yl.height-e.height&&(h="bottom");var c=(u.left+u.right)/2,f=(u.top+u.bottom)/2;"center"===h?(n=function(t){return t<=c},i=function(t){return t>c}):(n=function(t){return t<=e.width/2},i=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},r=function(t){return t-e.width-s.caretSize-s.caretPadding<0},o=function(t){return t<=f?"top":"bottom"},n(s.x)?(d="left",a(s.x)&&(d="center",h=o(s.y))):i(s.x)&&(d="right",r(s.x)&&(d="center",h=o(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:h}}(this,x),h._chart)}else g.opacity=0;return g.xAlign=v.xAlign,g.yAlign=v.yAlign,g.x=b.x,g.y=b.y,g.width=x.width,g.height=x.height,g.caretX=y.x,g.caretY=y.y,h._model=g,t&&c.custom&&c.custom.call(h,g),h},drawCaret:function(t,e){var n=this._chart.ctx,i=this._view,a=this.getCaretPosition(t,e,i);n.lineTo(a.x1,a.y1),n.lineTo(a.x2,a.y2),n.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,n){var i,a,r,o,s,l,u=n.caretSize,d=n.cornerRadius,h=n.xAlign,c=n.yAlign,f=t.x,g=t.y,p=e.width,m=e.height;if("center"===c)s=g+m/2,"left"===h?(a=(i=f)-u,r=i,o=s+u,l=s-u):(a=(i=f+p)+u,r=i,o=s-u,l=s+u);else if("left"===h?(i=(a=f+d+u)-u,r=a+u):"right"===h?(i=(a=f+p-d-u)-u,r=a+u):(i=(a=n.caretX)-u,r=a+u),"top"===c)s=(o=g)-u,l=o;else{s=(o=g+m)+u,l=o;var v=r;r=i,i=v}return{x1:i,x2:a,x3:r,y1:o,y2:s,y3:l}},drawTitle:function(t,e,n){var i,a,r,o=e.title,s=o.length;if(s){var l=ze(e.rtl,e.x,e.width);for(t.x=Ve(e,e._titleAlign),n.textAlign=l.textAlign(e._titleAlign),n.textBaseline="middle",i=e.titleFontSize,a=e.titleSpacing,n.fillStyle=e.titleFontColor,n.font=V.fontString(i,e._titleFontStyle,e._titleFontFamily),r=0;r0&&n.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&r&&(t.save(),t.globalAlpha=a,this.drawBackground(i,e,t,n),i.y+=e.yPadding,V.rtl.overrideTextDirection(t,e.textDirection),this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),V.rtl.restoreTextDirection(t,e.textDirection),t.restore())}},handleEvent:function(t){var e,n=this,i=n._options;return n._lastActive=n._lastActive||[],"mouseout"===t.type?n._active=[]:(n._active=n._chart.getElementsAtEventForMode(t,i.mode,i),i.reverse&&n._active.reverse()),(e=!V.arrayEquals(n._active,n._lastActive))&&(n._lastActive=n._active,(i.enabled||i.custom)&&(n._eventPosition={x:t.x,y:t.y},n.update(!0),n.pivot())),e}}),qe=Ne,Ue=je;Ue.positioners=qe;var Ye=V.valueOrDefault;function Ge(){return V.merge({},[].slice.call(arguments),{merger:function(t,e,n,i){if("xAxes"===t||"yAxes"===t){var a,r,o,s=n[t].length;for(e[t]||(e[t]=[]),a=0;a=e[t].length&&e[t].push({}),!e[t][a].type||o.type&&o.type!==e[t][a].type?V.merge(e[t][a],[Oe.getScaleDefaults(r),o]):V.merge(e[t][a],o)}else V._merger(t,e,n,i)}})}function Xe(){return V.merge({},[].slice.call(arguments),{merger:function(t,e,n,i){var a=e[t]||{},r=n[t];"scales"===t?e[t]=Ge(a,r):"scale"===t?e[t]=V.merge(a,[Oe.getScaleDefaults(r.type),r]):V._merger(t,e,n,i)}})}function Ke(t){var e=t.options;V.each(t.scales,(function(e){ge.removeBox(t,e)})),e=Xe(z.global,z[t.config.type],e),t.options=t.config.options=e,t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.tooltip._options=e.tooltips,t.tooltip.initialize()}function Ze(t,e,n){var i,a=function(t){return t.id===i};do{i=e+n++}while(V.findIndex(t,a)>=0);return i}function $e(t){return"top"===t||"bottom"===t}function Je(t,e){return function(n,i){return n[t]===i[t]?n[e]-i[e]:n[t]-i[t]}}z._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var Qe=function(t,e){return this.construct(t,e),this};V.extend(Qe.prototype,{construct:function(t,e){var n=this;e=function(t){var e=(t=t||{}).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=Xe(z.global,z[t.type],t.options||{}),t}(e);var i=Fe.acquireContext(t,e),a=i&&i.canvas,r=a&&a.height,o=a&&a.width;n.id=V.uid(),n.ctx=i,n.canvas=a,n.config=e,n.width=o,n.height=r,n.aspectRatio=r?o/r:null,n.options=e.options,n._bufferedRender=!1,n._layers=[],n.chart=n,n.controller=n,Qe.instances[n.id]=n,Object.defineProperty(n,"data",{get:function(){return n.config.data},set:function(t){n.config.data=t}}),i&&a?(n.initialize(),n.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return Le.notify(t,"beforeInit"),V.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.initToolTip(),Le.notify(t,"afterInit"),t},clear:function(){return V.canvas.clear(this),this},stop:function(){return $.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,a=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(V.getMaximumWidth(i))),o=Math.max(0,Math.floor(a?r/a:V.getMaximumHeight(i)));if((e.width!==r||e.height!==o)&&(i.width=e.width=r,i.height=e.height=o,i.style.width=r+"px",i.style.height=o+"px",V.retinaScale(e,n.devicePixelRatio),!t)){var s={width:r,height:o};Le.notify(e,"resize",[s]),n.onResize&&n.onResize(e,s),e.stop(),e.update({duration:n.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;V.each(e.xAxes,(function(t,n){t.id||(t.id=Ze(e.xAxes,"x-axis-",n))})),V.each(e.yAxes,(function(t,n){t.id||(t.id=Ze(e.yAxes,"y-axis-",n))})),n&&(n.id=n.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,n=t.scales||{},i=[],a=Object.keys(n).reduce((function(t,e){return t[e]=!1,t}),{});e.scales&&(i=i.concat((e.scales.xAxes||[]).map((function(t){return{options:t,dtype:"category",dposition:"bottom"}})),(e.scales.yAxes||[]).map((function(t){return{options:t,dtype:"linear",dposition:"left"}})))),e.scale&&i.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),V.each(i,(function(e){var i=e.options,r=i.id,o=Ye(i.type,e.dtype);$e(i.position)!==$e(e.dposition)&&(i.position=e.dposition),a[r]=!0;var s=null;if(r in n&&n[r].type===o)(s=n[r]).options=i,s.ctx=t.ctx,s.chart=t;else{var l=Oe.getScaleConstructor(o);if(!l)return;s=new l({id:r,type:o,options:i,ctx:t.ctx,chart:t}),n[s.id]=s}s.mergeTicksOptions(),e.isDefault&&(t.scale=s)})),V.each(a,(function(t,e){t||delete n[e]})),t.scales=n,Oe.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t,e,n=this,i=[],a=n.data.datasets;for(t=0,e=a.length;t=0;--n)this.drawDataset(e[n],t);Le.notify(this,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n={meta:t,index:t.index,easingValue:e};!1!==Le.notify(this,"beforeDatasetDraw",[n])&&(t.controller.draw(e),Le.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,n={tooltip:e,easingValue:t};!1!==Le.notify(this,"beforeTooltipDraw",[n])&&(e.draw(),Le.notify(this,"afterTooltipDraw",[n]))},getElementAtEvent:function(t){return ae.modes.single(this,t)},getElementsAtEvent:function(t){return ae.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return ae.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=ae.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return ae.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var n=e._meta[this.id];return n||(n=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e.order||0,index:t}),n},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e3?n[2]-n[1]:n[1]-n[0];Math.abs(i)>1&&t!==Math.floor(t)&&(i=t-Math.floor(t));var a=V.log10(Math.abs(i)),r="";if(0!==t)if(Math.max(Math.abs(n[0]),Math.abs(n[n.length-1]))<1e-4){var o=V.log10(Math.abs(t)),s=Math.floor(o)-Math.floor(a);s=Math.max(Math.min(s,20),0),r=t.toExponential(s)}else{var l=-1*Math.floor(a);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var i=t/Math.pow(10,Math.floor(V.log10(t)));return 0===t?"0":1===i||2===i||5===i||0===e||e===n.length-1?t.toExponential():""}}},on=V.isArray,sn=V.isNullOrUndef,ln=V.valueOrDefault,un=V.valueAtIndexOrDefault;function dn(t,e,n){var i,a=t.getTicks().length,r=Math.min(e,a-1),o=t.getPixelForTick(r),s=t._startPixel,l=t._endPixel;if(!(n&&(i=1===a?Math.max(o-s,l-o):0===e?(t.getPixelForTick(1)-o)/2:(o-t.getPixelForTick(r-1))/2,(o+=rl+1e-6)))return o}function hn(t,e,n,i){var a,r,o,s,l,u,d,h,c,f,g,p,m,v=n.length,b=[],x=[],y=[];for(a=0;ae){for(n=0;n=c||d<=1||!s.isHorizontal()?s.labelRotation=h:(e=(t=s._getLabelSizes()).widest.width,n=t.highest.height-t.highest.offset,i=Math.min(s.maxWidth,s.chart.width-e),e+6>(a=l.offset?s.maxWidth/d:i/(d-1))&&(a=i/(d-(l.offset?.5:1)),r=s.maxHeight-cn(l.gridLines)-u.padding-fn(l.scaleLabel),o=Math.sqrt(e*e+n*n),f=V.toDegrees(Math.min(Math.asin(Math.min((t.highest.height+6)/a,1)),Math.asin(Math.min(r/o,1))-Math.asin(n/o))),f=Math.max(h,Math.min(c,f))),s.labelRotation=f)},afterCalculateTickRotation:function(){V.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){V.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},n=t.chart,i=t.options,a=i.ticks,r=i.scaleLabel,o=i.gridLines,s=t._isVisible(),l="bottom"===i.position,u=t.isHorizontal();if(u?e.width=t.maxWidth:s&&(e.width=cn(o)+fn(r)),u?s&&(e.height=cn(o)+fn(r)):e.height=t.maxHeight,a.display&&s){var d=pn(a),h=t._getLabelSizes(),c=h.first,f=h.last,g=h.widest,p=h.highest,m=.4*d.minor.lineHeight,v=a.padding;if(u){var b=0!==t.labelRotation,x=V.toRadians(t.labelRotation),y=Math.cos(x),_=Math.sin(x),k=_*g.width+y*(p.height-(b?p.offset:0))+(b?0:m);e.height=Math.min(t.maxHeight,e.height+k+v);var w,M,S=t.getPixelForTick(0)-t.left,C=t.right-t.getPixelForTick(t.getTicks().length-1);b?(w=l?y*c.width+_*c.offset:_*(c.height-c.offset),M=l?_*(f.height-f.offset):y*f.width+_*f.offset):(w=c.width/2,M=f.width/2),t.paddingLeft=Math.max((w-S)*t.width/(t.width-S),0)+3,t.paddingRight=Math.max((M-C)*t.width/(t.width-C),0)+3}else{var P=a.mirror?0:g.width+v+m;e.width=Math.min(t.maxWidth,e.width+P),t.paddingTop=c.height/2,t.paddingBottom=f.height/2}}t.handleMargins(),u?(t.width=t._length=n.width-t.margins.left-t.margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=n.height-t.margins.top-t.margins.bottom)},handleMargins:function(){var t=this;t.margins&&(t.margins.left=Math.max(t.paddingLeft,t.margins.left),t.margins.top=Math.max(t.paddingTop,t.margins.top),t.margins.right=Math.max(t.paddingRight,t.margins.right),t.margins.bottom=Math.max(t.paddingBottom,t.margins.bottom))},afterFit:function(){V.callback(this.options.afterFit,[this])},isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(sn(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},_convertTicksToLabels:function(t){var e,n,i,a=this;for(a.ticks=t.map((function(t){return t.value})),a.beforeTickToLabelConversion(),e=a.convertTicksToLabels(t)||a.ticks,a.afterTickToLabelConversion(),n=0,i=t.length;nn-1?null:this.getPixelForDecimal(t*i+(e?i/2:0))},getPixelForDecimal:function(t){return this._reversePixels&&(t=1-t),this._startPixel+t*this._length},getDecimalForPixel:function(t){var e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,n,i,a,r=this.options.ticks,o=this._length,s=r.maxTicksLimit||o/this._tickSize()+1,l=r.major.enabled?function(t){var e,n,i=[];for(e=0,n=t.length;es)return function(t,e,n){var i,a,r=0,o=e[0];for(n=Math.ceil(n),i=0;iu)return r;return Math.max(u,1)}(l,t,0,s),u>0){for(e=0,n=u-1;e1?(h-d)/(u-1):null,vn(t,i,V.isNullOrUndef(a)?0:d-a,d),vn(t,i,h,V.isNullOrUndef(a)?t.length:h+a),mn(t)}return vn(t,i),mn(t)},_tickSize:function(){var t=this.options.ticks,e=V.toRadians(this.labelRotation),n=Math.abs(Math.cos(e)),i=Math.abs(Math.sin(e)),a=this._getLabelSizes(),r=t.autoSkipPadding||0,o=a?a.widest.width+r:0,s=a?a.highest.height+r:0;return this.isHorizontal()?s*n>o*i?o/n:s/i:s*i=0&&(o=t),void 0!==r&&(t=n.indexOf(r))>=0&&(s=t),e.minIndex=o,e.maxIndex=s,e.min=n[o],e.max=n[s]},buildTicks:function(){var t=this._getLabels(),e=this.minIndex,n=this.maxIndex;this.ticks=0===e&&n===t.length-1?t:t.slice(e,n+1)},getLabelForIndex:function(t,e){var n=this.chart;return n.getDatasetMeta(e).controller._getValueScaleId()===this.id?this.getRightValue(n.data.datasets[e].data[t]):this._getLabels()[t]},_configure:function(){var t=this,e=t.options.offset,n=t.ticks;xn.prototype._configure.call(t),t.isHorizontal()||(t._reversePixels=!t._reversePixels),n&&(t._startValue=t.minIndex-(e?.5:0),t._valueRange=Math.max(n.length-(e?0:1),1))},getPixelForValue:function(t,e,n){var i,a,r,o=this;return yn(e)||yn(n)||(t=o.chart.data.datasets[n].data[e]),yn(t)||(i=o.isHorizontal()?t.x:t.y),(void 0!==i||void 0!==t&&isNaN(e))&&(a=o._getLabels(),t=V.valueOrDefault(i,t),e=-1!==(r=a.indexOf(t))?r:e,isNaN(e)&&(e=t)),o.getPixelForDecimal((e-o._startValue)/o._valueRange)},getPixelForTick:function(t){var e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t],t+this.minIndex)},getValueForPixel:function(t){var e=Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange);return Math.min(Math.max(e,0),this.ticks.length-1)},getBasePixel:function(){return this.bottom}}),kn={position:"bottom"};_n._defaults=kn;var wn=V.noop,Mn=V.isNullOrUndef;var Sn=xn.extend({getRightValue:function(t){return"string"==typeof t?+t:xn.prototype.getRightValue.call(this,t)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=V.sign(t.min),i=V.sign(t.max);n<0&&i<0?t.max=0:n>0&&i>0&&(t.min=0)}var a=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),a!==r&&t.min>=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this.options.ticks,n=e.stepSize,i=e.maxTicksLimit;return n?t=Math.ceil(this.max/n)-Math.floor(this.min/n)+1:(t=this._computeTickLimit(),i=i||11),i&&(t=Math.min(i,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:wn,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),i={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,precision:e.precision,stepSize:V.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var n,i,a,r,o=[],s=t.stepSize,l=s||1,u=t.maxTicks-1,d=t.min,h=t.max,c=t.precision,f=e.min,g=e.max,p=V.niceNum((g-f)/u/l)*l;if(p<1e-14&&Mn(d)&&Mn(h))return[f,g];(r=Math.ceil(g/p)-Math.floor(f/p))>u&&(p=V.niceNum(r*p/u/l)*l),s||Mn(c)?n=Math.pow(10,V._decimalPlaces(p)):(n=Math.pow(10,c),p=Math.ceil(p*n)/n),i=Math.floor(f/p)*p,a=Math.ceil(g/p)*p,s&&(!Mn(d)&&V.almostWhole(d/p,p/1e3)&&(i=d),!Mn(h)&&V.almostWhole(h/p,p/1e3)&&(a=h)),r=(a-i)/p,r=V.almostEquals(r,Math.round(r),p/1e3)?Math.round(r):Math.ceil(r),i=Math.round(i*n)/n,a=Math.round(a*n)/n,o.push(Mn(d)?i:d);for(var m=1;me.length-1?null:this.getPixelForValue(e[t])}}),Tn=Cn;Dn._defaults=Tn;var In=V.valueOrDefault,Fn=V.math.log10;var Ln={position:"left",ticks:{callback:rn.formatters.logarithmic}};function On(t,e){return V.isFinite(t)&&t>=0?t:e}var Rn=xn.extend({determineDataLimits:function(){var t,e,n,i,a,r,o=this,s=o.options,l=o.chart,u=l.data.datasets,d=o.isHorizontal();function h(t){return d?t.xAxisID===o.id:t.yAxisID===o.id}o.min=Number.POSITIVE_INFINITY,o.max=Number.NEGATIVE_INFINITY,o.minNotZero=Number.POSITIVE_INFINITY;var c=s.stacked;if(void 0===c)for(t=0;t0){var e=V.min(t),n=V.max(t);o.min=Math.min(o.min,e),o.max=Math.max(o.max,n)}}))}else for(t=0;t0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(Fn(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,n=!t.isHorizontal(),i={min:On(e.min),max:On(e.max)},a=t.ticks=function(t,e){var n,i,a=[],r=In(t.min,Math.pow(10,Math.floor(Fn(e.min)))),o=Math.floor(Fn(e.max)),s=Math.ceil(e.max/Math.pow(10,o));0===r?(n=Math.floor(Fn(e.minNotZero)),i=Math.floor(e.minNotZero/Math.pow(10,n)),a.push(r),r=i*Math.pow(10,n)):(n=Math.floor(Fn(r)),i=Math.floor(r/Math.pow(10,n)));var l=n<0?Math.pow(10,Math.abs(n)):1;do{a.push(r),10===++i&&(i=1,l=++n>=0?1:l),r=Math.round(i*Math.pow(10,n)*l)/l}while(ne.length-1?null:this.getPixelForValue(e[t])},_getFirstTickValue:function(t){var e=Math.floor(Fn(t));return Math.floor(t/Math.pow(10,e))*Math.pow(10,e)},_configure:function(){var t=this,e=t.min,n=0;xn.prototype._configure.call(t),0===e&&(e=t._getFirstTickValue(t.minNotZero),n=In(t.options.ticks.fontSize,z.global.defaultFontSize)/t._length),t._startValue=Fn(e),t._valueOffset=n,t._valueRange=(Fn(t.max)-Fn(e))/(1-n)},getPixelForValue:function(t){var e=this,n=0;return(t=+e.getRightValue(t))>e.min&&t>0&&(n=(Fn(t)-e._startValue)/e._valueRange+e._valueOffset),e.getPixelForDecimal(n)},getValueForPixel:function(t){var e=this,n=e.getDecimalForPixel(t);return 0===n&&0===e.min?0:Math.pow(10,e._startValue+(n-e._valueOffset)*e._valueRange)}}),zn=Ln;Rn._defaults=zn;var Nn=V.valueOrDefault,Bn=V.valueAtIndexOrDefault,En=V.options.resolve,Wn={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:rn.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(t){return t}}};function Vn(t){var e=t.ticks;return e.display&&t.display?Nn(e.fontSize,z.global.defaultFontSize)+2*e.backdropPaddingY:0}function Hn(t,e,n,i,a){return t===i||t===a?{start:e-n/2,end:e+n/2}:ta?{start:e-n,end:e}:{start:e,end:e+n}}function jn(t){return 0===t||180===t?"center":t<180?"left":"right"}function qn(t,e,n,i){var a,r,o=n.y+i/2;if(V.isArray(e))for(a=0,r=e.length;a270||t<90)&&(n.y-=e.h)}function Yn(t){return V.isNumber(t)?t:0}var Gn=Sn.extend({setDimensions:function(){var t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=Vn(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2},determineDataLimits:function(){var t=this,e=t.chart,n=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;V.each(e.data.datasets,(function(a,r){if(e.isDatasetVisible(r)){var o=e.getDatasetMeta(r);V.each(a.data,(function(e,a){var r=+t.getRightValue(e);isNaN(r)||o.data[a].hidden||(n=Math.min(r,n),i=Math.max(r,i))}))}})),t.min=n===Number.POSITIVE_INFINITY?0:n,t.max=i===Number.NEGATIVE_INFINITY?0:i,t.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Vn(this.options))},convertTicksToLabels:function(){var t=this;Sn.prototype.convertTicksToLabels.call(t),t.pointLabels=t.chart.data.labels.map((function(){var e=V.callback(t.options.pointLabels.callback,arguments,t);return e||0===e?e:""}))},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t=this.options;t.display&&t.pointLabels.display?function(t){var e,n,i,a=V.options._parseFont(t.options.pointLabels),r={l:0,r:t.width,t:0,b:t.height-t.paddingTop},o={};t.ctx.font=a.string,t._pointLabelSizes=[];var s,l,u,d=t.chart.data.labels.length;for(e=0;er.r&&(r.r=f.end,o.r=h),g.startr.b&&(r.b=g.end,o.b=h)}t.setReductions(t.drawingArea,r,o)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(t,e,n){var i=this,a=e.l/Math.sin(n.l),r=Math.max(e.r-i.width,0)/Math.sin(n.r),o=-e.t/Math.cos(n.t),s=-Math.max(e.b-(i.height-i.paddingTop),0)/Math.cos(n.b);a=Yn(a),r=Yn(r),o=Yn(o),s=Yn(s),i.drawingArea=Math.min(Math.floor(t-(a+r)/2),Math.floor(t-(o+s)/2)),i.setCenterPoint(a,r,o,s)},setCenterPoint:function(t,e,n,i){var a=this,r=a.width-e-a.drawingArea,o=t+a.drawingArea,s=n+a.drawingArea,l=a.height-a.paddingTop-i-a.drawingArea;a.xCenter=Math.floor((o+r)/2+a.left),a.yCenter=Math.floor((s+l)/2+a.top+a.paddingTop)},getIndexAngle:function(t){var e=this.chart,n=(t*(360/e.data.labels.length)+((e.options||{}).startAngle||0))%360;return(n<0?n+360:n)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(V.isNullOrUndef(t))return NaN;var n=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*n:(t-e.min)*n},getPointPosition:function(t,e){var n=this.getIndexAngle(t)-Math.PI/2;return{x:Math.cos(n)*e+this.xCenter,y:Math.sin(n)*e+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(t){var e=this.min,n=this.max;return this.getPointPositionForValue(t||0,this.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0)},_drawGrid:function(){var t,e,n,i=this,a=i.ctx,r=i.options,o=r.gridLines,s=r.angleLines,l=Nn(s.lineWidth,o.lineWidth),u=Nn(s.color,o.color);if(r.pointLabels.display&&function(t){var e=t.ctx,n=t.options,i=n.pointLabels,a=Vn(n),r=t.getDistanceFromCenterForValue(n.ticks.reverse?t.min:t.max),o=V.options._parseFont(i);e.save(),e.font=o.string,e.textBaseline="middle";for(var s=t.chart.data.labels.length-1;s>=0;s--){var l=0===s?a/2:0,u=t.getPointPosition(s,r+l+5),d=Bn(i.fontColor,s,z.global.defaultFontColor);e.fillStyle=d;var h=t.getIndexAngle(s),c=V.toDegrees(h);e.textAlign=jn(c),Un(c,t._pointLabelSizes[s],u),qn(e,t.pointLabels[s],u,o.lineHeight)}e.restore()}(i),o.display&&V.each(i.ticks,(function(t,n){0!==n&&(e=i.getDistanceFromCenterForValue(i.ticksAsNumbers[n]),function(t,e,n,i){var a,r=t.ctx,o=e.circular,s=t.chart.data.labels.length,l=Bn(e.color,i-1),u=Bn(e.lineWidth,i-1);if((o||s)&&l&&u){if(r.save(),r.strokeStyle=l,r.lineWidth=u,r.setLineDash&&(r.setLineDash(e.borderDash||[]),r.lineDashOffset=e.borderDashOffset||0),r.beginPath(),o)r.arc(t.xCenter,t.yCenter,n,0,2*Math.PI);else{a=t.getPointPosition(0,n),r.moveTo(a.x,a.y);for(var d=1;d=0;t--)e=i.getDistanceFromCenterForValue(r.ticks.reverse?i.min:i.max),n=i.getPointPosition(t,e),a.beginPath(),a.moveTo(i.xCenter,i.yCenter),a.lineTo(n.x,n.y),a.stroke();a.restore()}},_drawLabels:function(){var t=this,e=t.ctx,n=t.options.ticks;if(n.display){var i,a,r=t.getIndexAngle(0),o=V.options._parseFont(n),s=Nn(n.fontColor,z.global.defaultFontColor);e.save(),e.font=o.string,e.translate(t.xCenter,t.yCenter),e.rotate(r),e.textAlign="center",e.textBaseline="middle",V.each(t.ticks,(function(r,l){(0!==l||n.reverse)&&(i=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),n.showLabelBackdrop&&(a=e.measureText(r).width,e.fillStyle=n.backdropColor,e.fillRect(-a/2-n.backdropPaddingX,-i-o.size/2-n.backdropPaddingY,a+2*n.backdropPaddingX,o.size+2*n.backdropPaddingY)),e.fillStyle=s,e.fillText(r,0,-i))})),e.restore()}},_drawTitle:V.noop}),Xn=Wn;Gn._defaults=Xn;var Kn=V._deprecated,Zn=V.options.resolve,$n=V.valueOrDefault,Jn=Number.MIN_SAFE_INTEGER||-9007199254740991,Qn=Number.MAX_SAFE_INTEGER||9007199254740991,ti={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ei=Object.keys(ti);function ni(t,e){return t-e}function ii(t){return V.valueOrDefault(t.time.min,t.ticks.min)}function ai(t){return V.valueOrDefault(t.time.max,t.ticks.max)}function ri(t,e,n,i){var a=function(t,e,n){for(var i,a,r,o=0,s=t.length-1;o>=0&&o<=s;){if(a=t[(i=o+s>>1)-1]||null,r=t[i],!a)return{lo:null,hi:r};if(r[e]n))return{lo:a,hi:r};s=i-1}}return{lo:r,hi:null}}(t,e,n),r=a.lo?a.hi?a.lo:t[t.length-2]:t[0],o=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=o[e]-r[e],l=s?(n-r[e])/s:0,u=(o[i]-r[i])*l;return r[i]+u}function oi(t,e){var n=t._adapter,i=t.options.time,a=i.parser,r=a||i.format,o=e;return"function"==typeof a&&(o=a(o)),V.isFinite(o)||(o="string"==typeof r?n.parse(o,r):n.parse(o)),null!==o?+o:(a||"function"!=typeof r||(o=r(e),V.isFinite(o)||(o=n.parse(o))),o)}function si(t,e){if(V.isNullOrUndef(e))return null;var n=t.options.time,i=oi(t,t.getRightValue(e));return null===i?i:(n.round&&(i=+t._adapter.startOf(i,n.round)),i)}function li(t,e,n,i){var a,r,o,s=ei.length;for(a=ei.indexOf(t);a=0&&(e[r].major=!0);return e}(t,r,o,n):r}var di=xn.extend({initialize:function(){this.mergeTicksOptions(),xn.prototype.initialize.call(this)},update:function(){var t=this,e=t.options,n=e.time||(e.time={}),i=t._adapter=new an._date(e.adapters.date);return Kn("time scale",n.format,"time.format","time.parser"),Kn("time scale",n.min,"time.min","ticks.min"),Kn("time scale",n.max,"time.max","ticks.max"),V.mergeIf(n.displayFormats,i.formats()),xn.prototype.update.apply(t,arguments)},getRightValue:function(t){return t&&void 0!==t.t&&(t=t.t),xn.prototype.getRightValue.call(this,t)},determineDataLimits:function(){var t,e,n,i,a,r,o,s=this,l=s.chart,u=s._adapter,d=s.options,h=d.time.unit||"day",c=Qn,f=Jn,g=[],p=[],m=[],v=s._getLabels();for(t=0,n=v.length;t1?function(t){var e,n,i,a={},r=[];for(e=0,n=t.length;e1e5*u)throw e+" and "+n+" are too far apart with stepSize of "+u+" "+l;for(a=h;a=a&&n<=r&&d.push(n);return i.min=a,i.max=r,i._unit=l.unit||(s.autoSkip?li(l.minUnit,i.min,i.max,h):function(t,e,n,i,a){var r,o;for(r=ei.length-1;r>=ei.indexOf(n);r--)if(o=ei[r],ti[o].common&&t._adapter.diff(a,i,o)>=e-1)return o;return ei[n?ei.indexOf(n):0]}(i,d.length,l.minUnit,i.min,i.max)),i._majorUnit=s.major.enabled&&"year"!==i._unit?function(t){for(var e=ei.indexOf(t)+1,n=ei.length;ee&&s=0&&t0?s:1}}),hi={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};di._defaults=hi;var ci={category:_n,linear:Dn,logarithmic:Rn,radialLinear:Gn,time:di},fi={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};an._date.override("function"==typeof t?{_id:"moment",formats:function(){return fi},parse:function(e,n){return"string"==typeof e&&"string"==typeof n?e=t(e,n):e instanceof t||(e=t(e)),e.isValid()?e.valueOf():null},format:function(e,n){return t(e).format(n)},add:function(e,n,i){return t(e).add(n,i).valueOf()},diff:function(e,n,i){return t(e).diff(t(n),i)},startOf:function(e,n,i){return e=t(e),"isoWeek"===n?e.isoWeekday(i).valueOf():e.startOf(n).valueOf()},endOf:function(e,n){return t(e).endOf(n).valueOf()},_create:function(e){return t(e)}}:{}),z._set("global",{plugins:{filler:{propagate:!0}}});var gi={dataset:function(t){var e=t.fill,n=t.chart,i=n.getDatasetMeta(e),a=i&&n.isDatasetVisible(e)&&i.dataset._children||[],r=a.length||0;return r?function(t,e){return e=n)&&i;switch(r){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return r;default:return!1}}function mi(t){return(t.el._scale||{}).getPointPositionForValue?function(t){var e,n,i,a,r,o=t.el._scale,s=o.options,l=o.chart.data.labels.length,u=t.fill,d=[];if(!l)return null;for(e=s.ticks.reverse?o.max:o.min,n=s.ticks.reverse?o.min:o.max,i=o.getPointPositionForValue(0,e),a=0;a0;--r)V.canvas.lineTo(t,n[r],n[r-1],!0);else for(o=n[0].cx,s=n[0].cy,l=Math.sqrt(Math.pow(n[0].x-o,2)+Math.pow(n[0].y-s,2)),r=a-1;r>0;--r)t.arc(o,s,l,n[r].angle,n[r-1].angle,!0)}}function _i(t,e,n,i,a,r){var o,s,l,u,d,h,c,f,g=e.length,p=i.spanGaps,m=[],v=[],b=0,x=0;for(t.beginPath(),o=0,s=g;o=0;--n)(e=l[n].$filler)&&e.visible&&(a=(i=e.el)._view,r=i._children||[],o=e.mapper,s=a.backgroundColor||z.global.defaultColor,o&&s&&r.length&&(V.canvas.clipArea(u,t.chartArea),_i(u,r,o,a,s,i._loop),V.canvas.unclipArea(u)))}},wi=V.rtl.getRtlAdapter,Mi=V.noop,Si=V.valueOrDefault;function Ci(t,e){return t.usePointStyle&&t.boxWidth>e?e:t.boxWidth}z._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data.datasets,n=t.options.legend||{},i=n.labels&&n.labels.usePointStyle;return t._getSortedDatasetMetas().map((function(n){var a=n.controller.getStyle(i?0:void 0);return{text:e[n.index].label,fillStyle:a.backgroundColor,hidden:!t.isDatasetVisible(n.index),lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,pointStyle:a.pointStyle,rotation:a.rotation,datasetIndex:n.index}}),this)}}},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data.datasets;for(a.setAttribute("class",t.id+"-legend"),e=0,n=r.length;el.width)&&(h+=o+n.padding,d[d.length-(e>0?0:1)]=0),s[e]={left:0,top:0,width:i,height:o},d[d.length-1]+=i+n.padding})),l.height+=h}else{var c=n.padding,f=t.columnWidths=[],g=t.columnHeights=[],p=n.padding,m=0,v=0;V.each(t.legendItems,(function(t,e){var i=Ci(n,o)+o/2+a.measureText(t.text).width;e>0&&v+o+2*c>l.height&&(p+=m+n.padding,f.push(m),g.push(v),m=0,v=0),m=Math.max(m,i),v+=o+c,s[e]={left:0,top:0,width:i,height:o}})),p+=m,f.push(m),g.push(v),l.width+=p}t.width=l.width,t.height=l.height}else t.width=l.width=t.height=l.height=0},afterFit:Mi,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,n=e.labels,i=z.global,a=i.defaultColor,r=i.elements.line,o=t.height,s=t.columnHeights,l=t.width,u=t.lineWidths;if(e.display){var d,h=wi(e.rtl,t.left,t.minSize.width),c=t.ctx,f=Si(n.fontColor,i.defaultFontColor),g=V.options._parseFont(n),p=g.size;c.textAlign=h.textAlign("left"),c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=g.string;var m=Ci(n,p),v=t.legendHitBoxes,b=function(t,i){switch(e.align){case"start":return n.padding;case"end":return t-i;default:return(t-i+n.padding)/2}},x=t.isHorizontal();d=x?{x:t.left+b(l,u[0]),y:t.top+n.padding,line:0}:{x:t.left+n.padding,y:t.top+b(o,s[0]),line:0},V.rtl.overrideTextDirection(t.ctx,e.textDirection);var y=p+n.padding;V.each(t.legendItems,(function(e,i){var f=c.measureText(e.text).width,g=m+p/2+f,_=d.x,k=d.y;h.setWidth(t.minSize.width),x?i>0&&_+g+n.padding>t.left+t.minSize.width&&(k=d.y+=y,d.line++,_=d.x=t.left+b(l,u[d.line])):i>0&&k+y>t.top+t.minSize.height&&(_=d.x=_+t.columnWidths[d.line]+n.padding,d.line++,k=d.y=t.top+b(o,s[d.line]));var w=h.x(_);!function(t,e,i){if(!(isNaN(m)||m<=0)){c.save();var o=Si(i.lineWidth,r.borderWidth);if(c.fillStyle=Si(i.fillStyle,a),c.lineCap=Si(i.lineCap,r.borderCapStyle),c.lineDashOffset=Si(i.lineDashOffset,r.borderDashOffset),c.lineJoin=Si(i.lineJoin,r.borderJoinStyle),c.lineWidth=o,c.strokeStyle=Si(i.strokeStyle,a),c.setLineDash&&c.setLineDash(Si(i.lineDash,r.borderDash)),n&&n.usePointStyle){var s=m*Math.SQRT2/2,l=h.xPlus(t,m/2),u=e+p/2;V.canvas.drawPoint(c,i.pointStyle,s,l,u,i.rotation)}else c.fillRect(h.leftForLtr(t,m),e,m,p),0!==o&&c.strokeRect(h.leftForLtr(t,m),e,m,p);c.restore()}}(w,k,e),v[i].left=h.leftForLtr(w,v[i].width),v[i].top=k,function(t,e,n,i){var a=p/2,r=h.xPlus(t,m+a),o=e+a;c.fillText(n.text,r,o),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(r,o),c.lineTo(h.xPlus(r,i),o),c.stroke())}(w,k,e,f),x?d.x+=g+n.padding:d.y+=y})),V.rtl.restoreTextDirection(t.ctx,e.textDirection)}},_getLegendItemAt:function(t,e){var n,i,a,r=this;if(t>=r.left&&t<=r.right&&e>=r.top&&e<=r.bottom)for(a=r.legendHitBoxes,n=0;n=(i=a[n]).left&&t<=i.left+i.width&&e>=i.top&&e<=i.top+i.height)return r.legendItems[n];return null},handleEvent:function(t){var e,n=this,i=n.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!i.onHover&&!i.onLeave)return}else{if("click"!==a)return;if(!i.onClick)return}e=n._getLegendItemAt(t.x,t.y),"click"===a?e&&i.onClick&&i.onClick.call(n,t.native,e):(i.onLeave&&e!==n._hoveredItem&&(n._hoveredItem&&i.onLeave.call(n,t.native,n._hoveredItem),n._hoveredItem=e),i.onHover&&e&&i.onHover.call(n,t.native,e))}});function Ai(t,e){var n=new Pi({ctx:t.ctx,options:e,chart:t});ge.configure(t,n,e),ge.addBox(t,n),t.legend=n}var Di={id:"legend",_element:Pi,beforeInit:function(t){var e=t.options.legend;e&&Ai(t,e)},beforeUpdate:function(t){var e=t.options.legend,n=t.legend;e?(V.mergeIf(e,z.global.legend),n?(ge.configure(t,n,e),n.options=e):Ai(t,e)):n&&(ge.removeBox(t,n),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}},Ti=V.noop;z._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Ii=X.extend({initialize:function(t){V.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:Ti,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Ti,beforeSetDimensions:Ti,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Ti,beforeBuildLabels:Ti,buildLabels:Ti,afterBuildLabels:Ti,beforeFit:Ti,fit:function(){var t,e=this,n=e.options,i=e.minSize={},a=e.isHorizontal();n.display?(t=(V.isArray(n.text)?n.text.length:1)*V.options._parseFont(n).lineHeight+2*n.padding,e.width=i.width=a?e.maxWidth:t,e.height=i.height=a?t:e.maxHeight):e.width=i.width=e.height=i.height=0},afterFit:Ti,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=t.options;if(n.display){var i,a,r,o=V.options._parseFont(n),s=o.lineHeight,l=s/2+n.padding,u=0,d=t.top,h=t.left,c=t.bottom,f=t.right;e.fillStyle=V.valueOrDefault(n.fontColor,z.global.defaultFontColor),e.font=o.string,t.isHorizontal()?(a=h+(f-h)/2,r=d+l,i=f-h):(a="left"===n.position?h+l:f-l,r=d+(c-d)/2,i=c-d,u=Math.PI*("left"===n.position?-.5:.5)),e.save(),e.translate(a,r),e.rotate(u),e.textAlign="center",e.textBaseline="middle";var g=n.text;if(V.isArray(g))for(var p=0,m=0;m=0;i--){var a=t[i];if(e(a))return a}},V.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},V.almostEquals=function(t,e,n){return Math.abs(t-e)=t},V.max=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.max(t,e)}),Number.NEGATIVE_INFINITY)},V.min=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.min(t,e)}),Number.POSITIVE_INFINITY)},V.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0===(t=+t)||isNaN(t)?t:t>0?1:-1},V.toRadians=function(t){return t*(Math.PI/180)},V.toDegrees=function(t){return t*(180/Math.PI)},V._decimalPlaces=function(t){if(V.isFinite(t)){for(var e=1,n=0;Math.round(t*e)/e!==t;)e*=10,n++;return n}},V.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},V.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},V.aliasPixel=function(t){return t%2==0?0:.5},V._alignPixel=function(t,e,n){var i=t.currentDevicePixelRatio,a=n/2;return Math.round((e-a)*i)/i+a},V.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l),h=i*(u=isNaN(u)?0:u),c=i*(d=isNaN(d)?0:d);return{previous:{x:r.x-h*(o.x-a.x),y:r.y-h*(o.y-a.y)},next:{x:r.x+c*(o.x-a.x),y:r.y+c*(o.y-a.y)}}},V.EPSILON=Number.EPSILON||1e-14,V.splineCurveMonotone=function(t){var e,n,i,a,r,o,s,l,u,d=(t||[]).map((function(t){return{model:t._model,deltaK:0,mK:0}})),h=d.length;for(e=0;e0?d[e-1]:null,(a=e0?d[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},V.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},V.niceNum=function(t,e){var n=Math.floor(V.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},V.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},V.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.target||t.srcElement,o=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var l=parseFloat(V.getStyle(r,"padding-left")),u=parseFloat(V.getStyle(r,"padding-top")),d=parseFloat(V.getStyle(r,"padding-right")),h=parseFloat(V.getStyle(r,"padding-bottom")),c=o.right-o.left-l-d,f=o.bottom-o.top-u-h;return{x:n=Math.round((n-o.left-l)/c*r.width/e.currentDevicePixelRatio),y:i=Math.round((i-o.top-u)/f*r.height/e.currentDevicePixelRatio)}},V.getConstraintWidth=function(t){return n(t,"max-width","clientWidth")},V.getConstraintHeight=function(t){return n(t,"max-height","clientHeight")},V._calculatePadding=function(t,e,n){return(e=V.getStyle(t,e)||"0").indexOf("%")>-1?n*parseInt(e,10)/100:parseInt(e,10)},V._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},V.getMaximumWidth=function(t){var e=V._getParentNode(t);if(!e)return t.clientWidth;var n=e.clientWidth,i=n-V._calculatePadding(e,"padding-left",n)-V._calculatePadding(e,"padding-right",n),a=V.getConstraintWidth(t);return isNaN(a)?i:Math.min(i,a)},V.getMaximumHeight=function(t){var e=V._getParentNode(t);if(!e)return t.clientHeight;var n=e.clientHeight,i=n-V._calculatePadding(e,"padding-top",n)-V._calculatePadding(e,"padding-bottom",n),a=V.getConstraintHeight(t);return isNaN(a)?i:Math.min(i,a)},V.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},V.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,r=t.width;i.height=a*n,i.width=r*n,t.ctx.scale(n,n),i.style.height||i.style.width||(i.style.height=a+"px",i.style.width=r+"px")}},V.fontString=function(t,e,n){return e+" "+t+"px "+n},V.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var o,s,l,u,d,h=0,c=n.length;for(o=0;on.length){for(o=0;oi&&(i=r),i},V.numberOfLabelLines=function(t){var e=1;return V.each(t,(function(t){V.isArray(t)&&t.length>e&&(e=t.length)})),e},V.color=k?function(t){return t instanceof CanvasGradient&&(t=z.global.defaultColor),k(t)}:function(t){return console.error("Color.js not found!"),t},V.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:V.color(t).saturate(.5).darken(.1).rgbString()}}(),tn._adapters=an,tn.Animation=Z,tn.animationService=$,tn.controllers=$t,tn.DatasetController=nt,tn.defaults=z,tn.Element=X,tn.elements=_t,tn.Interaction=ae,tn.layouts=ge,tn.platform=Fe,tn.plugins=Le,tn.Scale=xn,tn.scaleService=Oe,tn.Ticks=rn,tn.Tooltip=Ue,tn.helpers.each(ci,(function(t,e){tn.scaleService.registerScaleType(e,t,t._defaults)})),Li)Li.hasOwnProperty(Ni)&&tn.plugins.register(Li[Ni]);tn.platform.initialize();var Bi=tn;return"undefined"!=typeof window&&(window.Chart=tn),tn.Chart=tn,tn.Legend=Li.legend._element,tn.Title=Li.title._element,tn.pluginService=tn.plugins,tn.PluginBase=tn.Element.extend({}),tn.canvasHelpers=tn.helpers.canvas,tn.layoutService=tn.layouts,tn.LinearScaleBase=Sn,tn.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],(function(t){tn[t]=function(e,n){return new tn(e,tn.helpers.merge(n||{},{type:t.charAt(0).toLowerCase()+t.slice(1)}))}})),Bi})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(function(){try{return require("moment")}catch(t){}}()):"function"==typeof define&&define.amd?define(["require"],(function(t){return e(function(){try{return t("moment")}catch(t){}}())})):(t=t||self).Chart=e(t.moment)}(this,(function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var e={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},n=function(t,e){return t(e={exports:{}},e.exports),e.exports}((function(t){var n={};for(var i in e)e.hasOwnProperty(i)&&(n[e[i]]=i);var a=t.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var r in a)if(a.hasOwnProperty(r)){if(!("channels"in a[r]))throw new Error("missing channels property: "+r);if(!("labels"in a[r]))throw new Error("missing channel labels property: "+r);if(a[r].labels.length!==a[r].channels)throw new Error("channel and label counts mismatch: "+r);var o=a[r].channels,s=a[r].labels;delete a[r].channels,delete a[r].labels,Object.defineProperty(a[r],"channels",{value:o}),Object.defineProperty(a[r],"labels",{value:s})}a.rgb.hsl=function(t){var e,n,i=t[0]/255,a=t[1]/255,r=t[2]/255,o=Math.min(i,a,r),s=Math.max(i,a,r),l=s-o;return s===o?e=0:i===s?e=(a-r)/l:a===s?e=2+(r-i)/l:r===s&&(e=4+(i-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),n=(o+s)/2,[e,100*(s===o?0:n<=.5?l/(s+o):l/(2-s-o)),100*n]},a.rgb.hsv=function(t){var e,n,i,a,r,o=t[0]/255,s=t[1]/255,l=t[2]/255,u=Math.max(o,s,l),d=u-Math.min(o,s,l),h=function(t){return(u-t)/6/d+.5};return 0===d?a=r=0:(r=d/u,e=h(o),n=h(s),i=h(l),o===u?a=i-n:s===u?a=1/3+e-i:l===u&&(a=2/3+n-e),a<0?a+=1:a>1&&(a-=1)),[360*a,100*r,100*u]},a.rgb.hwb=function(t){var e=t[0],n=t[1],i=t[2];return[a.rgb.hsl(t)[0],100*(1/255*Math.min(e,Math.min(n,i))),100*(i=1-1/255*Math.max(e,Math.max(n,i)))]},a.rgb.cmyk=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255;return[100*((1-n-(e=Math.min(1-n,1-i,1-a)))/(1-e)||0),100*((1-i-e)/(1-e)||0),100*((1-a-e)/(1-e)||0),100*e]},a.rgb.keyword=function(t){var i=n[t];if(i)return i;var a,r,o,s=1/0;for(var l in e)if(e.hasOwnProperty(l)){var u=e[l],d=(r=t,o=u,Math.pow(r[0]-o[0],2)+Math.pow(r[1]-o[1],2)+Math.pow(r[2]-o[2],2));d.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]},a.rgb.lab=function(t){var e=a.rgb.xyz(t),n=e[0],i=e[1],r=e[2];return i/=100,r/=108.883,n=(n/=95.047)>.008856?Math.pow(n,1/3):7.787*n+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(n-i),200*(i-(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116))]},a.hsl.rgb=function(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0===s)return[r=255*l,r,r];e=2*l-(n=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(i=o+1/3*-(u-1))<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a},a.hsl.hsv=function(t){var e=t[0],n=t[1]/100,i=t[2]/100,a=n,r=Math.max(i,.01);return n*=(i*=2)<=1?i:2-i,a*=r<=1?r:2-r,[e,100*(0===i?2*a/(r+a):2*n/(i+n)),100*((i+n)/2)]},a.hsv.rgb=function(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r));switch(i*=255,a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}},a.hsv.hsl=function(t){var e,n,i,a=t[0],r=t[1]/100,o=t[2]/100,s=Math.max(o,.01);return i=(2-r)*o,n=r*s,[a,100*(n=(n/=(e=(2-r)*s)<=1?e:2-e)||0),100*(i/=2)]},a.hwb.rgb=function(t){var e,n,i,a,r,o,s,l=t[0]/360,u=t[1]/100,d=t[2]/100,h=u+d;switch(h>1&&(u/=h,d/=h),i=6*l-(e=Math.floor(6*l)),0!=(1&e)&&(i=1-i),a=u+i*((n=1-d)-u),e){default:case 6:case 0:r=n,o=a,s=u;break;case 1:r=a,o=n,s=u;break;case 2:r=u,o=n,s=a;break;case 3:r=u,o=a,s=n;break;case 4:r=a,o=u,s=n;break;case 5:r=n,o=u,s=a}return[255*r,255*o,255*s]},a.cmyk.rgb=function(t){var e=t[0]/100,n=t[1]/100,i=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a))]},a.xyz.rgb=function(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return n=-.9689*a+1.8758*r+.0415*o,i=.0557*a+-.204*r+1.057*o,e=(e=3.2406*a+-1.5372*r+-.4986*o)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:12.92*i,[255*(e=Math.min(Math.max(0,e),1)),255*(n=Math.min(Math.max(0,n),1)),255*(i=Math.min(Math.max(0,i),1))]},a.xyz.lab=function(t){var e=t[0],n=t[1],i=t[2];return n/=100,i/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(e-n),200*(n-(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116))]},a.lab.xyz=function(t){var e,n,i,a=t[0];e=t[1]/500+(n=(a+16)/116),i=n-t[2]/200;var r=Math.pow(n,3),o=Math.pow(e,3),s=Math.pow(i,3);return n=r>.008856?r:(n-16/116)/7.787,e=o>.008856?o:(e-16/116)/7.787,i=s>.008856?s:(i-16/116)/7.787,[e*=95.047,n*=100,i*=108.883]},a.lab.lch=function(t){var e,n=t[0],i=t[1],a=t[2];return(e=360*Math.atan2(a,i)/2/Math.PI)<0&&(e+=360),[n,Math.sqrt(i*i+a*a),e]},a.lch.lab=function(t){var e,n=t[0],i=t[1];return e=t[2]/360*2*Math.PI,[n,i*Math.cos(e),i*Math.sin(e)]},a.rgb.ansi16=function(t){var e=t[0],n=t[1],i=t[2],r=1 in arguments?arguments[1]:a.rgb.hsv(t)[2];if(0===(r=Math.round(r/50)))return 30;var o=30+(Math.round(i/255)<<2|Math.round(n/255)<<1|Math.round(e/255));return 2===r&&(o+=60),o},a.hsv.ansi16=function(t){return a.rgb.ansi16(a.hsv.rgb(t),t[2])},a.rgb.ansi256=function(t){var e=t[0],n=t[1],i=t[2];return e===n&&n===i?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(n/255*5)+Math.round(i/255*5)},a.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var n=.5*(1+~~(t>50));return[(1&e)*n*255,(e>>1&1)*n*255,(e>>2&1)*n*255]},a.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var n;return t-=16,[Math.floor(t/36)/5*255,Math.floor((n=t%36)/6)/5*255,n%6/5*255]},a.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},a.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var n=e[0];3===e[0].length&&(n=n.split("").map((function(t){return t+t})).join(""));var i=parseInt(n,16);return[i>>16&255,i>>8&255,255&i]},a.rgb.hcg=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255,r=Math.max(Math.max(n,i),a),o=Math.min(Math.min(n,i),a),s=r-o;return e=s<=0?0:r===n?(i-a)/s%6:r===i?2+(a-n)/s:4+(n-i)/s+4,e/=6,[360*(e%=1),100*s,100*(s<1?o/(1-s):0)]},a.hsl.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=1,a=0;return(i=n<.5?2*e*n:2*e*(1-n))<1&&(a=(n-.5*i)/(1-i)),[t[0],100*i,100*a]},a.hsv.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=e*n,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.hcg.rgb=function(t){var e=t[0]/360,n=t[1]/100,i=t[2]/100;if(0===n)return[255*i,255*i,255*i];var a,r=[0,0,0],o=e%1*6,s=o%1,l=1-s;switch(Math.floor(o)){case 0:r[0]=1,r[1]=s,r[2]=0;break;case 1:r[0]=l,r[1]=1,r[2]=0;break;case 2:r[0]=0,r[1]=1,r[2]=s;break;case 3:r[0]=0,r[1]=l,r[2]=1;break;case 4:r[0]=s,r[1]=0,r[2]=1;break;default:r[0]=1,r[1]=0,r[2]=l}return a=(1-n)*i,[255*(n*r[0]+a),255*(n*r[1]+a),255*(n*r[2]+a)]},a.hcg.hsv=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e),i=0;return n>0&&(i=e/n),[t[0],100*i,100*n]},a.hcg.hsl=function(t){var e=t[1]/100,n=t[2]/100*(1-e)+.5*e,i=0;return n>0&&n<.5?i=e/(2*n):n>=.5&&n<1&&(i=e/(2*(1-n))),[t[0],100*i,100*n]},a.hcg.hwb=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e);return[t[0],100*(n-e),100*(1-n)]},a.hwb.hcg=function(t){var e=t[1]/100,n=1-t[2]/100,i=n-e,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},a.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},a.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},a.gray.hsl=a.gray.hsv=function(t){return[0,0,t[0]]},a.gray.hwb=function(t){return[0,100,t[0]]},a.gray.cmyk=function(t){return[0,0,0,t[0]]},a.gray.lab=function(t){return[t[0],0,0]},a.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),n=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(n.length)+n},a.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}}));n.rgb,n.hsl,n.hsv,n.hwb,n.cmyk,n.xyz,n.lab,n.lch,n.hex,n.keyword,n.ansi16,n.ansi256,n.hcg,n.apple,n.gray;function i(t){var e=function(){for(var t={},e=Object.keys(n),i=e.length,a=0;a1&&(e=Array.prototype.slice.call(arguments));var n=t(e);if("object"==typeof n)for(var i=n.length,a=0;a1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}(i)}))}));var s=o,l={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},u={getRgba:d,getHsla:h,getRgb:function(t){var e=d(t);return e&&e.slice(0,3)},getHsl:function(t){var e=h(t);return e&&e.slice(0,3)},getHwb:c,getAlpha:function(t){var e=d(t);if(e)return e[3];if(e=h(t))return e[3];if(e=c(t))return e[3]},hexString:function(t,e){e=void 0!==e&&3===t.length?e:t[3];return"#"+v(t[0])+v(t[1])+v(t[2])+(e>=0&&e<1?v(Math.round(255*e)):"")},rgbString:function(t,e){if(e<1||t[3]&&t[3]<1)return f(t,e);return"rgb("+t[0]+", "+t[1]+", "+t[2]+")"},rgbaString:f,percentString:function(t,e){if(e<1||t[3]&&t[3]<1)return g(t,e);var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+n+"%, "+i+"%, "+a+"%)"},percentaString:g,hslString:function(t,e){if(e<1||t[3]&&t[3]<1)return p(t,e);return"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"},hslaString:p,hwbString:function(t,e){void 0===e&&(e=void 0!==t[3]?t[3]:1);return"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"},keyword:function(t){return b[t.slice(0,3)]}};function d(t){if(t){var e=[0,0,0],n=1,i=t.match(/^#([a-fA-F0-9]{3,4})$/i),a="";if(i){a=(i=i[1])[3];for(var r=0;rn?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=t,i=void 0===e?.5:e,a=2*i-1,r=this.alpha()-n.alpha(),o=((a*r==-1?a:(a+r)/(1+a*r))+1)/2,s=1-o;return this.rgb(o*this.red()+s*n.red(),o*this.green()+s*n.green(),o*this.blue()+s*n.blue()).alpha(this.alpha()*i+n.alpha()*(1-i))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new y,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},y.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},y.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},y.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i=0;a--)e.call(n,t[a],a);else for(a=0;a=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-C.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*C.easeInBounce(2*t):.5*C.easeOutBounce(2*t-1)+.5}},P={effects:C};S.easingEffects=C;var A=Math.PI,D=A/180,T=2*A,I=A/2,F=A/4,O=2*A/3,L={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,r){if(r){var o=Math.min(r,a/2,i/2),s=e+o,l=n+o,u=e+i-o,d=n+a-o;t.moveTo(e,l),se.left-1e-6&&t.xe.top-1e-6&&t.y0&&this.requestAnimationFrame()},advance:function(){for(var t,e,n,i,a=this.animations,r=0;r=n?(H.callback(t.onAnimationComplete,[t],e),e.animating=!1,a.splice(r,1)):++r}},Q=H.options.resolve,tt=["push","pop","shift","splice","unshift"];function et(t,e){var n=t._chartjs;if(n){var i=n.listeners,a=i.indexOf(e);-1!==a&&i.splice(a,1),i.length>0||(tt.forEach((function(e){delete t[e]})),delete t._chartjs)}}var nt=function(t,e){this.initialize(t,e)};H.extend(nt.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements(),n._type=n.getMeta().type},updateIndex:function(t){this.index=t},linkScales:function(){var t=this.getMeta(),e=this.chart,n=e.scales,i=this.getDataset(),a=e.options.scales;null!==t.xAxisID&&t.xAxisID in n&&!i.xAxisID||(t.xAxisID=i.xAxisID||a.xAxes[0].id),null!==t.yAxisID&&t.yAxisID in n&&!i.yAxisID||(t.yAxisID=i.yAxisID||a.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&et(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,n=this.getMeta(),i=this.getDataset().data||[],a=n.data;for(t=0,e=i.length;tn&&this.insertElements(n,i-n)},insertElements:function(t,e){for(var n=0;na?(r=a/e.innerRadius,t.arc(o,s,e.innerRadius-a,i+r,n-r,!0)):t.arc(o,s,a,i+Math.PI/2,n-Math.PI/2),t.closePath(),t.clip()}function ot(t,e,n){var i="inner"===e.borderAlign;i?(t.lineWidth=2*e.borderWidth,t.lineJoin="round"):(t.lineWidth=e.borderWidth,t.lineJoin="bevel"),n.fullCircles&&function(t,e,n,i){var a,r=n.endAngle;for(i&&(n.endAngle=n.startAngle+at,rt(t,n),n.endAngle=r,n.endAngle===n.startAngle&&n.fullCircles&&(n.endAngle+=at,n.fullCircles--)),t.beginPath(),t.arc(n.x,n.y,n.innerRadius,n.startAngle+at,n.startAngle,!0),a=0;as;)a-=at;for(;a=o&&a<=s,u=r>=n.innerRadius&&r<=n.outerRadius;return l&&u}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t,e=this._chart.ctx,n=this._view,i="inner"===n.borderAlign?.33:0,a={x:n.x,y:n.y,innerRadius:n.innerRadius,outerRadius:Math.max(n.outerRadius-i,0),pixelMargin:i,startAngle:n.startAngle,endAngle:n.endAngle,fullCircles:Math.floor(n.circumference/at)};if(e.save(),e.fillStyle=n.backgroundColor,e.strokeStyle=n.borderColor,a.fullCircles){for(a.endAngle=a.startAngle+at,e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),t=0;tt.x&&(e=bt(e,"left","right")):t.basen?n:i,r:l.right||a<0?0:a>e?e:a,b:l.bottom||r<0?0:r>n?n:r,l:l.left||o<0?0:o>e?e:o}}function yt(t,e,n){var i=null===e,a=null===n,r=!(!t||i&&a)&&vt(t);return r&&(i||e>=r.left&&e<=r.right)&&(a||n>=r.top&&n<=r.bottom)}N._set("global",{elements:{rectangle:{backgroundColor:pt,borderColor:pt,borderSkipped:"bottom",borderWidth:0}}});var _t=K.extend({_type:"rectangle",draw:function(){var t=this._chart.ctx,e=this._view,n=function(t){var e=vt(t),n=e.right-e.left,i=e.bottom-e.top,a=xt(t,n/2,i/2);return{outer:{x:e.left,y:e.top,w:n,h:i},inner:{x:e.left+a.l,y:e.top+a.t,w:n-a.l-a.r,h:i-a.t-a.b}}}(e),i=n.outer,a=n.inner;t.fillStyle=e.backgroundColor,t.fillRect(i.x,i.y,i.w,i.h),i.w===a.w&&i.h===a.h||(t.save(),t.beginPath(),t.rect(i.x,i.y,i.w,i.h),t.clip(),t.fillStyle=e.borderColor,t.rect(a.x,a.y,a.w,a.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return yt(this._view,t,e)},inLabelRange:function(t,e){var n=this._view;return mt(n)?yt(n,t,null):yt(n,null,e)},inXRange:function(t){return yt(this._view,t,null)},inYRange:function(t){return yt(this._view,null,t)},getCenterPoint:function(){var t,e,n=this._view;return mt(n)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return mt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),kt={},wt=st,Mt=dt,St=gt,Ct=_t;kt.Arc=wt,kt.Line=Mt,kt.Point=St,kt.Rectangle=Ct;var Pt=H._deprecated,At=H.valueOrDefault;function Dt(t,e,n){var i,a,r=n.barThickness,o=e.stackCount,s=e.pixels[t],l=H.isNullOrUndef(r)?function(t,e){var n,i,a,r,o=t._length;for(a=1,r=e.length;a0?Math.min(o,Math.abs(i-n)):o,n=i;return o}(e.scale,e.pixels):-1;return H.isNullOrUndef(r)?(i=l*n.categoryPercentage,a=n.barPercentage):(i=r*o,a=1),{chunk:i/o,ratio:a,start:s-i/2}}N._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),N._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var Tt=it.extend({dataElementType:kt.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var t,e,n=this;it.prototype.initialize.apply(n,arguments),(t=n.getMeta()).stack=n.getDataset().stack,t.bar=!0,e=n._getIndexScale().options,Pt("bar chart",e.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),Pt("bar chart",e.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),Pt("bar chart",e.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),Pt("bar chart",n._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),Pt("bar chart",e.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(t){var e,n,i=this.getMeta().data;for(this._ruler=this.getRuler(),e=0,n=i.length;e=0&&p.min>=0?p.min:p.max,y=void 0===p.start?p.end:p.max>=0&&p.min>=0?p.max-p.min:p.min-p.max,_=g.length;if(v||void 0===v&&void 0!==b)for(i=0;i<_&&(a=g[i]).index!==t;++i)a.stack===b&&(r=void 0===(u=h._parseValue(f[a.index].data[e])).start?u.end:u.min>=0&&u.max>=0?u.max:u.min,(p.min<0&&r<0||p.max>=0&&r>0)&&(x+=r));return o=h.getPixelForValue(x),l=(s=h.getPixelForValue(x+y))-o,void 0!==m&&Math.abs(l)=0&&!c||y<0&&c?o-m:o+m),{size:l,base:o,head:s,center:s+l/2}},calculateBarIndexPixels:function(t,e,n,i){var a="flex"===i.barThickness?function(t,e,n){var i,a=e.pixels,r=a[t],o=t>0?a[t-1]:null,s=t=Rt?-zt:b<-Rt?zt:0)+m,y=Math.cos(b),_=Math.sin(b),k=Math.cos(x),w=Math.sin(x),M=b<=0&&x>=0||x>=zt,S=b<=Nt&&x>=Nt||x>=zt+Nt,C=b<=-Nt&&x>=-Nt||x>=Rt+Nt,P=b===-Rt||x>=Rt?-1:Math.min(y,y*p,k,k*p),A=C?-1:Math.min(_,_*p,w,w*p),D=M?1:Math.max(y,y*p,k,k*p),T=S?1:Math.max(_,_*p,w,w*p);u=(D-P)/2,d=(T-A)/2,h=-(D+P)/2,c=-(T+A)/2}for(i=0,a=g.length;i0&&!isNaN(t)?zt*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,n,i,a,r,o,s,l,u=0,d=this.chart;if(!t)for(e=0,n=d.data.datasets.length;e(u=s>u?s:u)?l:u);return u},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=Lt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Lt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Lt(n.hoverBorderWidth,n.borderWidth)},_getRingWeightOffset:function(t){for(var e=0,n=0;n0&&Ht(l[t-1]._model,s)&&(n.controlPointPreviousX=u(n.controlPointPreviousX,s.left,s.right),n.controlPointPreviousY=u(n.controlPointPreviousY,s.top,s.bottom)),t0&&(r=t.getDatasetMeta(r[0]._datasetIndex).data),r},"x-axis":function(t,e){return ae(t,e,{intersect:!1})},point:function(t,e){return ee(t,Qt(e,t))},nearest:function(t,e,n){var i=Qt(e,t);n.axis=n.axis||"xy";var a=ie(n.axis);return ne(t,i,n.intersect,a)},x:function(t,e,n){var i=Qt(e,t),a=[],r=!1;return te(t,(function(t){t.inXRange(i.x)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a},y:function(t,e,n){var i=Qt(e,t),a=[],r=!1;return te(t,(function(t){t.inYRange(i.y)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a}}},oe=H.extend;function se(t,e){return H.where(t,(function(t){return t.pos===e}))}function le(t,e){return t.sort((function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i.index-a.index:i.weight-a.weight}))}function ue(t,e,n,i){return Math.max(t[n],e[n])+Math.max(t[i],e[i])}function de(t,e,n){var i,a,r=n.box,o=t.maxPadding;if(n.size&&(t[n.pos]-=n.size),n.size=n.horizontal?r.height:r.width,t[n.pos]+=n.size,r.getPadding){var s=r.getPadding();o.top=Math.max(o.top,s.top),o.left=Math.max(o.left,s.left),o.bottom=Math.max(o.bottom,s.bottom),o.right=Math.max(o.right,s.right)}if(i=e.outerWidth-ue(o,t,"left","right"),a=e.outerHeight-ue(o,t,"top","bottom"),i!==t.w||a!==t.h){t.w=i,t.h=a;var l=n.horizontal?[i,t.w]:[a,t.h];return!(l[0]===l[1]||isNaN(l[0])&&isNaN(l[1]))}}function he(t,e){var n=e.maxPadding;function i(t){var i={left:0,top:0,right:0,bottom:0};return t.forEach((function(t){i[t]=Math.max(e[t],n[t])})),i}return i(t?["left","right"]:["top","bottom"])}function ce(t,e,n){var i,a,r,o,s,l,u=[];for(i=0,a=t.length;idiv{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}"}))&&ge.default||ge,ve="$chartjs",be="chartjs-size-monitor",xe="chartjs-render-monitor",ye="chartjs-render-animation",_e=["animationstart","webkitAnimationStart"],ke={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function we(t,e){var n=H.getStyle(t,e),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?Number(i[1]):void 0}var Me=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};function Se(t,e,n){t.addEventListener(e,n,Me)}function Ce(t,e,n){t.removeEventListener(e,n,Me)}function Pe(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function Ae(t){var e=document.createElement("div");return e.className=t||"",e}function De(t,e,n){var i,a,r,o,s=t[ve]||(t[ve]={}),l=s.resizer=function(t){var e=Ae(be),n=Ae(be+"-expand"),i=Ae(be+"-shrink");n.appendChild(Ae()),i.appendChild(Ae()),e.appendChild(n),e.appendChild(i),e._reset=function(){n.scrollLeft=1e6,n.scrollTop=1e6,i.scrollLeft=1e6,i.scrollTop=1e6};var a=function(){e._reset(),t()};return Se(n,"scroll",a.bind(n,"expand")),Se(i,"scroll",a.bind(i,"shrink")),e}((i=function(){if(s.resizer){var i=n.options.maintainAspectRatio&&t.parentNode,a=i?i.clientWidth:0;e(Pe("resize",n)),i&&i.clientWidth0){var r=t[0];r.label?n=r.label:r.xLabel?n=r.xLabel:a>0&&r.index-1?t.split("\n"):t}function Ve(t){var e=N.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,rtl:t.rtl,textDirection:t.textDirection,bodyFontColor:t.bodyFontColor,_bodyFontFamily:ze(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:ze(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:ze(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:ze(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:ze(t.titleFontStyle,e.defaultFontStyle),titleFontSize:ze(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:ze(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:ze(t.footerFontStyle,e.defaultFontStyle),footerFontSize:ze(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function He(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function je(t){return Ee([],We(t))}var qe=K.extend({initialize:function(){this._model=Ve(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options,n=e.callbacks,i=n.beforeTitle.apply(t,arguments),a=n.title.apply(t,arguments),r=n.afterTitle.apply(t,arguments),o=[];return o=Ee(o,We(i)),o=Ee(o,We(a)),o=Ee(o,We(r))},getBeforeBody:function(){return je(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var n=this,i=n._options.callbacks,a=[];return H.each(t,(function(t){var r={before:[],lines:[],after:[]};Ee(r.before,We(i.beforeLabel.call(n,t,e))),Ee(r.lines,i.label.call(n,t,e)),Ee(r.after,We(i.afterLabel.call(n,t,e))),a.push(r)})),a},getAfterBody:function(){return je(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this,e=t._options.callbacks,n=e.beforeFooter.apply(t,arguments),i=e.footer.apply(t,arguments),a=e.afterFooter.apply(t,arguments),r=[];return r=Ee(r,We(n)),r=Ee(r,We(i)),r=Ee(r,We(a))},update:function(t){var e,n,i,a,r,o,s,l,u,d,h=this,c=h._options,f=h._model,g=h._model=Ve(c),p=h._active,m=h._data,v={xAlign:f.xAlign,yAlign:f.yAlign},b={x:f.x,y:f.y},x={width:f.width,height:f.height},y={x:f.caretX,y:f.caretY};if(p.length){g.opacity=1;var _=[],k=[];y=Be[c.position].call(h,p,h._eventPosition);var w=[];for(e=0,n=p.length;ei.width&&(a=i.width-e.width),a<0&&(a=0)),"top"===d?r+=h:r-="bottom"===d?e.height+h:e.height/2,"center"===d?"left"===u?a+=h:"right"===u&&(a-=h):"left"===u?a-=c:"right"===u&&(a+=c),{x:a,y:r}}(g,x,v=function(t,e){var n,i,a,r,o,s=t._model,l=t._chart,u=t._chart.chartArea,d="center",h="center";s.yl.height-e.height&&(h="bottom");var c=(u.left+u.right)/2,f=(u.top+u.bottom)/2;"center"===h?(n=function(t){return t<=c},i=function(t){return t>c}):(n=function(t){return t<=e.width/2},i=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},r=function(t){return t-e.width-s.caretSize-s.caretPadding<0},o=function(t){return t<=f?"top":"bottom"},n(s.x)?(d="left",a(s.x)&&(d="center",h=o(s.y))):i(s.x)&&(d="right",r(s.x)&&(d="center",h=o(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:h}}(this,x),h._chart)}else g.opacity=0;return g.xAlign=v.xAlign,g.yAlign=v.yAlign,g.x=b.x,g.y=b.y,g.width=x.width,g.height=x.height,g.caretX=y.x,g.caretY=y.y,h._model=g,t&&c.custom&&c.custom.call(h,g),h},drawCaret:function(t,e){var n=this._chart.ctx,i=this._view,a=this.getCaretPosition(t,e,i);n.lineTo(a.x1,a.y1),n.lineTo(a.x2,a.y2),n.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,n){var i,a,r,o,s,l,u=n.caretSize,d=n.cornerRadius,h=n.xAlign,c=n.yAlign,f=t.x,g=t.y,p=e.width,m=e.height;if("center"===c)s=g+m/2,"left"===h?(a=(i=f)-u,r=i,o=s+u,l=s-u):(a=(i=f+p)+u,r=i,o=s-u,l=s+u);else if("left"===h?(i=(a=f+d+u)-u,r=a+u):"right"===h?(i=(a=f+p-d-u)-u,r=a+u):(i=(a=n.caretX)-u,r=a+u),"top"===c)s=(o=g)-u,l=o;else{s=(o=g+m)+u,l=o;var v=r;r=i,i=v}return{x1:i,x2:a,x3:r,y1:o,y2:s,y3:l}},drawTitle:function(t,e,n){var i,a,r,o=e.title,s=o.length;if(s){var l=Ne(e.rtl,e.x,e.width);for(t.x=He(e,e._titleAlign),n.textAlign=l.textAlign(e._titleAlign),n.textBaseline="middle",i=e.titleFontSize,a=e.titleSpacing,n.fillStyle=e.titleFontColor,n.font=H.fontString(i,e._titleFontStyle,e._titleFontFamily),r=0;r0&&n.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&r&&(t.save(),t.globalAlpha=a,this.drawBackground(i,e,t,n),i.y+=e.yPadding,H.rtl.overrideTextDirection(t,e.textDirection),this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),H.rtl.restoreTextDirection(t,e.textDirection),t.restore())}},handleEvent:function(t){var e,n=this,i=n._options;return n._lastActive=n._lastActive||[],"mouseout"===t.type?n._active=[]:(n._active=n._chart.getElementsAtEventForMode(t,i.mode,i),i.reverse&&n._active.reverse()),(e=!H.arrayEquals(n._active,n._lastActive))&&(n._lastActive=n._active,(i.enabled||i.custom)&&(n._eventPosition={x:t.x,y:t.y},n.update(!0),n.pivot())),e}}),Ue=Be,Ye=qe;Ye.positioners=Ue;var Ge=H.valueOrDefault;function Xe(){return H.merge(Object.create(null),[].slice.call(arguments),{merger:function(t,e,n,i){if("xAxes"===t||"yAxes"===t){var a,r,o,s=n[t].length;for(e[t]||(e[t]=[]),a=0;a=e[t].length&&e[t].push({}),!e[t][a].type||o.type&&o.type!==e[t][a].type?H.merge(e[t][a],[Re.getScaleDefaults(r),o]):H.merge(e[t][a],o)}else H._merger(t,e,n,i)}})}function Ke(){return H.merge(Object.create(null),[].slice.call(arguments),{merger:function(t,e,n,i){var a=e[t]||Object.create(null),r=n[t];"scales"===t?e[t]=Xe(a,r):"scale"===t?e[t]=H.merge(a,[Re.getScaleDefaults(r.type),r]):H._merger(t,e,n,i)}})}function Ze(t){var e=t.options;H.each(t.scales,(function(e){pe.removeBox(t,e)})),e=Ke(N.global,N[t.config.type],e),t.options=t.config.options=e,t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.tooltip._options=e.tooltips,t.tooltip.initialize()}function $e(t,e,n){var i,a=function(t){return t.id===i};do{i=e+n++}while(H.findIndex(t,a)>=0);return i}function Je(t){return"top"===t||"bottom"===t}function Qe(t,e){return function(n,i){return n[t]===i[t]?n[e]-i[e]:n[t]-i[t]}}N._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var tn=function(t,e){return this.construct(t,e),this};H.extend(tn.prototype,{construct:function(t,e){var n=this;e=function(t){var e=(t=t||Object.create(null)).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=Ke(N.global,N[t.type],t.options||{}),t}(e);var i=Oe.acquireContext(t,e),a=i&&i.canvas,r=a&&a.height,o=a&&a.width;n.id=H.uid(),n.ctx=i,n.canvas=a,n.config=e,n.width=o,n.height=r,n.aspectRatio=r?o/r:null,n.options=e.options,n._bufferedRender=!1,n._layers=[],n.chart=n,n.controller=n,tn.instances[n.id]=n,Object.defineProperty(n,"data",{get:function(){return n.config.data},set:function(t){n.config.data=t}}),i&&a?(n.initialize(),n.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return Le.notify(t,"beforeInit"),H.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.initToolTip(),Le.notify(t,"afterInit"),t},clear:function(){return H.canvas.clear(this),this},stop:function(){return J.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,a=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(H.getMaximumWidth(i))),o=Math.max(0,Math.floor(a?r/a:H.getMaximumHeight(i)));if((e.width!==r||e.height!==o)&&(i.width=e.width=r,i.height=e.height=o,i.style.width=r+"px",i.style.height=o+"px",H.retinaScale(e,n.devicePixelRatio),!t)){var s={width:r,height:o};Le.notify(e,"resize",[s]),n.onResize&&n.onResize(e,s),e.stop(),e.update({duration:n.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;H.each(e.xAxes,(function(t,n){t.id||(t.id=$e(e.xAxes,"x-axis-",n))})),H.each(e.yAxes,(function(t,n){t.id||(t.id=$e(e.yAxes,"y-axis-",n))})),n&&(n.id=n.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,n=t.scales||{},i=[],a=Object.keys(n).reduce((function(t,e){return t[e]=!1,t}),{});e.scales&&(i=i.concat((e.scales.xAxes||[]).map((function(t){return{options:t,dtype:"category",dposition:"bottom"}})),(e.scales.yAxes||[]).map((function(t){return{options:t,dtype:"linear",dposition:"left"}})))),e.scale&&i.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),H.each(i,(function(e){var i=e.options,r=i.id,o=Ge(i.type,e.dtype);Je(i.position)!==Je(e.dposition)&&(i.position=e.dposition),a[r]=!0;var s=null;if(r in n&&n[r].type===o)(s=n[r]).options=i,s.ctx=t.ctx,s.chart=t;else{var l=Re.getScaleConstructor(o);if(!l)return;s=new l({id:r,type:o,options:i,ctx:t.ctx,chart:t}),n[s.id]=s}s.mergeTicksOptions(),e.isDefault&&(t.scale=s)})),H.each(a,(function(t,e){t||delete n[e]})),t.scales=n,Re.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t,e,n=this,i=[],a=n.data.datasets;for(t=0,e=a.length;t=0;--n)this.drawDataset(e[n],t);Le.notify(this,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n={meta:t,index:t.index,easingValue:e};!1!==Le.notify(this,"beforeDatasetDraw",[n])&&(t.controller.draw(e),Le.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,n={tooltip:e,easingValue:t};!1!==Le.notify(this,"beforeTooltipDraw",[n])&&(e.draw(),Le.notify(this,"afterTooltipDraw",[n]))},getElementAtEvent:function(t){return re.modes.single(this,t)},getElementsAtEvent:function(t){return re.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return re.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=re.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return re.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var n=e._meta[this.id];return n||(n=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e.order||0,index:t}),n},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e3?n[2]-n[1]:n[1]-n[0];Math.abs(i)>1&&t!==Math.floor(t)&&(i=t-Math.floor(t));var a=H.log10(Math.abs(i)),r="";if(0!==t)if(Math.max(Math.abs(n[0]),Math.abs(n[n.length-1]))<1e-4){var o=H.log10(Math.abs(t)),s=Math.floor(o)-Math.floor(a);s=Math.max(Math.min(s,20),0),r=t.toExponential(s)}else{var l=-1*Math.floor(a);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var i=t/Math.pow(10,Math.floor(H.log10(t)));return 0===t?"0":1===i||2===i||5===i||0===e||e===n.length-1?t.toExponential():""}}},sn=H.isArray,ln=H.isNullOrUndef,un=H.valueOrDefault,dn=H.valueAtIndexOrDefault;function hn(t,e,n){var i,a=t.getTicks().length,r=Math.min(e,a-1),o=t.getPixelForTick(r),s=t._startPixel,l=t._endPixel;if(!(n&&(i=1===a?Math.max(o-s,l-o):0===e?(t.getPixelForTick(1)-o)/2:(o-t.getPixelForTick(r-1))/2,(o+=rl+1e-6)))return o}function cn(t,e,n,i){var a,r,o,s,l,u,d,h,c,f,g,p,m,v=n.length,b=[],x=[],y=[],_=0,k=0;for(a=0;ae){for(n=0;n=c||d<=1||!s.isHorizontal()?s.labelRotation=h:(e=(t=s._getLabelSizes()).widest.width,n=t.highest.height-t.highest.offset,i=Math.min(s.maxWidth,s.chart.width-e),e+6>(a=l.offset?s.maxWidth/d:i/(d-1))&&(a=i/(d-(l.offset?.5:1)),r=s.maxHeight-fn(l.gridLines)-u.padding-gn(l.scaleLabel),o=Math.sqrt(e*e+n*n),f=H.toDegrees(Math.min(Math.asin(Math.min((t.highest.height+6)/a,1)),Math.asin(Math.min(r/o,1))-Math.asin(n/o))),f=Math.max(h,Math.min(c,f))),s.labelRotation=f)},afterCalculateTickRotation:function(){H.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){H.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},n=t.chart,i=t.options,a=i.ticks,r=i.scaleLabel,o=i.gridLines,s=t._isVisible(),l="bottom"===i.position,u=t.isHorizontal();if(u?e.width=t.maxWidth:s&&(e.width=fn(o)+gn(r)),u?s&&(e.height=fn(o)+gn(r)):e.height=t.maxHeight,a.display&&s){var d=mn(a),h=t._getLabelSizes(),c=h.first,f=h.last,g=h.widest,p=h.highest,m=.4*d.minor.lineHeight,v=a.padding;if(u){var b=0!==t.labelRotation,x=H.toRadians(t.labelRotation),y=Math.cos(x),_=Math.sin(x),k=_*g.width+y*(p.height-(b?p.offset:0))+(b?0:m);e.height=Math.min(t.maxHeight,e.height+k+v);var w,M,S=t.getPixelForTick(0)-t.left,C=t.right-t.getPixelForTick(t.getTicks().length-1);b?(w=l?y*c.width+_*c.offset:_*(c.height-c.offset),M=l?_*(f.height-f.offset):y*f.width+_*f.offset):(w=c.width/2,M=f.width/2),t.paddingLeft=Math.max((w-S)*t.width/(t.width-S),0)+3,t.paddingRight=Math.max((M-C)*t.width/(t.width-C),0)+3}else{var P=a.mirror?0:g.width+v+m;e.width=Math.min(t.maxWidth,e.width+P),t.paddingTop=c.height/2,t.paddingBottom=f.height/2}}t.handleMargins(),u?(t.width=t._length=n.width-t.margins.left-t.margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=n.height-t.margins.top-t.margins.bottom)},handleMargins:function(){var t=this;t.margins&&(t.margins.left=Math.max(t.paddingLeft,t.margins.left),t.margins.top=Math.max(t.paddingTop,t.margins.top),t.margins.right=Math.max(t.paddingRight,t.margins.right),t.margins.bottom=Math.max(t.paddingBottom,t.margins.bottom))},afterFit:function(){H.callback(this.options.afterFit,[this])},isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(ln(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},_convertTicksToLabels:function(t){var e,n,i,a=this;for(a.ticks=t.map((function(t){return t.value})),a.beforeTickToLabelConversion(),e=a.convertTicksToLabels(t)||a.ticks,a.afterTickToLabelConversion(),n=0,i=t.length;nn-1?null:this.getPixelForDecimal(t*i+(e?i/2:0))},getPixelForDecimal:function(t){return this._reversePixels&&(t=1-t),this._startPixel+t*this._length},getDecimalForPixel:function(t){var e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,n,i,a,r=this.options.ticks,o=this._length,s=r.maxTicksLimit||o/this._tickSize()+1,l=r.major.enabled?function(t){var e,n,i=[];for(e=0,n=t.length;es)return function(t,e,n){var i,a,r=0,o=e[0];for(n=Math.ceil(n),i=0;iu)return r;return Math.max(u,1)}(l,t,0,s),u>0){for(e=0,n=u-1;e1?(h-d)/(u-1):null,bn(t,i,H.isNullOrUndef(a)?0:d-a,d),bn(t,i,h,H.isNullOrUndef(a)?t.length:h+a),vn(t)}return bn(t,i),vn(t)},_tickSize:function(){var t=this.options.ticks,e=H.toRadians(this.labelRotation),n=Math.abs(Math.cos(e)),i=Math.abs(Math.sin(e)),a=this._getLabelSizes(),r=t.autoSkipPadding||0,o=a?a.widest.width+r:0,s=a?a.highest.height+r:0;return this.isHorizontal()?s*n>o*i?o/n:s/i:s*i=0&&(o=t),void 0!==r&&(t=n.indexOf(r))>=0&&(s=t),e.minIndex=o,e.maxIndex=s,e.min=n[o],e.max=n[s]},buildTicks:function(){var t=this._getLabels(),e=this.minIndex,n=this.maxIndex;this.ticks=0===e&&n===t.length-1?t:t.slice(e,n+1)},getLabelForIndex:function(t,e){var n=this.chart;return n.getDatasetMeta(e).controller._getValueScaleId()===this.id?this.getRightValue(n.data.datasets[e].data[t]):this._getLabels()[t]},_configure:function(){var t=this,e=t.options.offset,n=t.ticks;yn.prototype._configure.call(t),t.isHorizontal()||(t._reversePixels=!t._reversePixels),n&&(t._startValue=t.minIndex-(e?.5:0),t._valueRange=Math.max(n.length-(e?0:1),1))},getPixelForValue:function(t,e,n){var i,a,r,o=this;return _n(e)||_n(n)||(t=o.chart.data.datasets[n].data[e]),_n(t)||(i=o.isHorizontal()?t.x:t.y),(void 0!==i||void 0!==t&&isNaN(e))&&(a=o._getLabels(),t=H.valueOrDefault(i,t),e=-1!==(r=a.indexOf(t))?r:e,isNaN(e)&&(e=t)),o.getPixelForDecimal((e-o._startValue)/o._valueRange)},getPixelForTick:function(t){var e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t],t+this.minIndex)},getValueForPixel:function(t){var e=Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange);return Math.min(Math.max(e,0),this.ticks.length-1)},getBasePixel:function(){return this.bottom}}),wn={position:"bottom"};kn._defaults=wn;var Mn=H.noop,Sn=H.isNullOrUndef;var Cn=yn.extend({getRightValue:function(t){return"string"==typeof t?+t:yn.prototype.getRightValue.call(this,t)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=H.sign(t.min),i=H.sign(t.max);n<0&&i<0?t.max=0:n>0&&i>0&&(t.min=0)}var a=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),a!==r&&t.min>=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this.options.ticks,n=e.stepSize,i=e.maxTicksLimit;return n?t=Math.ceil(this.max/n)-Math.floor(this.min/n)+1:(t=this._computeTickLimit(),i=i||11),i&&(t=Math.min(i,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:Mn,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),i={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,precision:e.precision,stepSize:H.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var n,i,a,r,o=[],s=t.stepSize,l=s||1,u=t.maxTicks-1,d=t.min,h=t.max,c=t.precision,f=e.min,g=e.max,p=H.niceNum((g-f)/u/l)*l;if(p<1e-14&&Sn(d)&&Sn(h))return[f,g];(r=Math.ceil(g/p)-Math.floor(f/p))>u&&(p=H.niceNum(r*p/u/l)*l),s||Sn(c)?n=Math.pow(10,H._decimalPlaces(p)):(n=Math.pow(10,c),p=Math.ceil(p*n)/n),i=Math.floor(f/p)*p,a=Math.ceil(g/p)*p,s&&(!Sn(d)&&H.almostWhole(d/p,p/1e3)&&(i=d),!Sn(h)&&H.almostWhole(h/p,p/1e3)&&(a=h)),r=(a-i)/p,r=H.almostEquals(r,Math.round(r),p/1e3)?Math.round(r):Math.ceil(r),i=Math.round(i*n)/n,a=Math.round(a*n)/n,o.push(Sn(d)?i:d);for(var m=1;me.length-1?null:this.getPixelForValue(e[t])}}),In=Pn;Tn._defaults=In;var Fn=H.valueOrDefault,On=H.math.log10;var Ln={position:"left",ticks:{callback:on.formatters.logarithmic}};function Rn(t,e){return H.isFinite(t)&&t>=0?t:e}var zn=yn.extend({determineDataLimits:function(){var t,e,n,i,a,r,o=this,s=o.options,l=o.chart,u=l.data.datasets,d=o.isHorizontal();function h(t){return d?t.xAxisID===o.id:t.yAxisID===o.id}o.min=Number.POSITIVE_INFINITY,o.max=Number.NEGATIVE_INFINITY,o.minNotZero=Number.POSITIVE_INFINITY;var c=s.stacked;if(void 0===c)for(t=0;t0){var e=H.min(t),n=H.max(t);o.min=Math.min(o.min,e),o.max=Math.max(o.max,n)}}))}else for(t=0;t0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(On(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,n=!t.isHorizontal(),i={min:Rn(e.min),max:Rn(e.max)},a=t.ticks=function(t,e){var n,i,a=[],r=Fn(t.min,Math.pow(10,Math.floor(On(e.min)))),o=Math.floor(On(e.max)),s=Math.ceil(e.max/Math.pow(10,o));0===r?(n=Math.floor(On(e.minNotZero)),i=Math.floor(e.minNotZero/Math.pow(10,n)),a.push(r),r=i*Math.pow(10,n)):(n=Math.floor(On(r)),i=Math.floor(r/Math.pow(10,n)));var l=n<0?Math.pow(10,Math.abs(n)):1;do{a.push(r),10===++i&&(i=1,l=++n>=0?1:l),r=Math.round(i*Math.pow(10,n)*l)/l}while(ne.length-1?null:this.getPixelForValue(e[t])},_getFirstTickValue:function(t){var e=Math.floor(On(t));return Math.floor(t/Math.pow(10,e))*Math.pow(10,e)},_configure:function(){var t=this,e=t.min,n=0;yn.prototype._configure.call(t),0===e&&(e=t._getFirstTickValue(t.minNotZero),n=Fn(t.options.ticks.fontSize,N.global.defaultFontSize)/t._length),t._startValue=On(e),t._valueOffset=n,t._valueRange=(On(t.max)-On(e))/(1-n)},getPixelForValue:function(t){var e=this,n=0;return(t=+e.getRightValue(t))>e.min&&t>0&&(n=(On(t)-e._startValue)/e._valueRange+e._valueOffset),e.getPixelForDecimal(n)},getValueForPixel:function(t){var e=this,n=e.getDecimalForPixel(t);return 0===n&&0===e.min?0:Math.pow(10,e._startValue+(n-e._valueOffset)*e._valueRange)}}),Nn=Ln;zn._defaults=Nn;var Bn=H.valueOrDefault,En=H.valueAtIndexOrDefault,Wn=H.options.resolve,Vn={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:on.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(t){return t}}};function Hn(t){var e=t.ticks;return e.display&&t.display?Bn(e.fontSize,N.global.defaultFontSize)+2*e.backdropPaddingY:0}function jn(t,e,n,i,a){return t===i||t===a?{start:e-n/2,end:e+n/2}:ta?{start:e-n,end:e}:{start:e,end:e+n}}function qn(t){return 0===t||180===t?"center":t<180?"left":"right"}function Un(t,e,n,i){var a,r,o=n.y+i/2;if(H.isArray(e))for(a=0,r=e.length;a270||t<90)&&(n.y-=e.h)}function Gn(t){return H.isNumber(t)?t:0}var Xn=Cn.extend({setDimensions:function(){var t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=Hn(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2},determineDataLimits:function(){var t=this,e=t.chart,n=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;H.each(e.data.datasets,(function(a,r){if(e.isDatasetVisible(r)){var o=e.getDatasetMeta(r);H.each(a.data,(function(e,a){var r=+t.getRightValue(e);isNaN(r)||o.data[a].hidden||(n=Math.min(r,n),i=Math.max(r,i))}))}})),t.min=n===Number.POSITIVE_INFINITY?0:n,t.max=i===Number.NEGATIVE_INFINITY?0:i,t.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Hn(this.options))},convertTicksToLabels:function(){var t=this;Cn.prototype.convertTicksToLabels.call(t),t.pointLabels=t.chart.data.labels.map((function(){var e=H.callback(t.options.pointLabels.callback,arguments,t);return e||0===e?e:""}))},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t=this.options;t.display&&t.pointLabels.display?function(t){var e,n,i,a=H.options._parseFont(t.options.pointLabels),r={l:0,r:t.width,t:0,b:t.height-t.paddingTop},o={};t.ctx.font=a.string,t._pointLabelSizes=[];var s,l,u,d=t.chart.data.labels.length;for(e=0;er.r&&(r.r=f.end,o.r=h),g.startr.b&&(r.b=g.end,o.b=h)}t.setReductions(t.drawingArea,r,o)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(t,e,n){var i=this,a=e.l/Math.sin(n.l),r=Math.max(e.r-i.width,0)/Math.sin(n.r),o=-e.t/Math.cos(n.t),s=-Math.max(e.b-(i.height-i.paddingTop),0)/Math.cos(n.b);a=Gn(a),r=Gn(r),o=Gn(o),s=Gn(s),i.drawingArea=Math.min(Math.floor(t-(a+r)/2),Math.floor(t-(o+s)/2)),i.setCenterPoint(a,r,o,s)},setCenterPoint:function(t,e,n,i){var a=this,r=a.width-e-a.drawingArea,o=t+a.drawingArea,s=n+a.drawingArea,l=a.height-a.paddingTop-i-a.drawingArea;a.xCenter=Math.floor((o+r)/2+a.left),a.yCenter=Math.floor((s+l)/2+a.top+a.paddingTop)},getIndexAngle:function(t){var e=this.chart,n=(t*(360/e.data.labels.length)+((e.options||{}).startAngle||0))%360;return(n<0?n+360:n)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(H.isNullOrUndef(t))return NaN;var n=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*n:(t-e.min)*n},getPointPosition:function(t,e){var n=this.getIndexAngle(t)-Math.PI/2;return{x:Math.cos(n)*e+this.xCenter,y:Math.sin(n)*e+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(t){var e=this.min,n=this.max;return this.getPointPositionForValue(t||0,this.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0)},_drawGrid:function(){var t,e,n,i=this,a=i.ctx,r=i.options,o=r.gridLines,s=r.angleLines,l=Bn(s.lineWidth,o.lineWidth),u=Bn(s.color,o.color);if(r.pointLabels.display&&function(t){var e=t.ctx,n=t.options,i=n.pointLabels,a=Hn(n),r=t.getDistanceFromCenterForValue(n.ticks.reverse?t.min:t.max),o=H.options._parseFont(i);e.save(),e.font=o.string,e.textBaseline="middle";for(var s=t.chart.data.labels.length-1;s>=0;s--){var l=0===s?a/2:0,u=t.getPointPosition(s,r+l+5),d=En(i.fontColor,s,N.global.defaultFontColor);e.fillStyle=d;var h=t.getIndexAngle(s),c=H.toDegrees(h);e.textAlign=qn(c),Yn(c,t._pointLabelSizes[s],u),Un(e,t.pointLabels[s],u,o.lineHeight)}e.restore()}(i),o.display&&H.each(i.ticks,(function(t,n){0!==n&&(e=i.getDistanceFromCenterForValue(i.ticksAsNumbers[n]),function(t,e,n,i){var a,r=t.ctx,o=e.circular,s=t.chart.data.labels.length,l=En(e.color,i-1),u=En(e.lineWidth,i-1);if((o||s)&&l&&u){if(r.save(),r.strokeStyle=l,r.lineWidth=u,r.setLineDash&&(r.setLineDash(e.borderDash||[]),r.lineDashOffset=e.borderDashOffset||0),r.beginPath(),o)r.arc(t.xCenter,t.yCenter,n,0,2*Math.PI);else{a=t.getPointPosition(0,n),r.moveTo(a.x,a.y);for(var d=1;d=0;t--)e=i.getDistanceFromCenterForValue(r.ticks.reverse?i.min:i.max),n=i.getPointPosition(t,e),a.beginPath(),a.moveTo(i.xCenter,i.yCenter),a.lineTo(n.x,n.y),a.stroke();a.restore()}},_drawLabels:function(){var t=this,e=t.ctx,n=t.options.ticks;if(n.display){var i,a,r=t.getIndexAngle(0),o=H.options._parseFont(n),s=Bn(n.fontColor,N.global.defaultFontColor);e.save(),e.font=o.string,e.translate(t.xCenter,t.yCenter),e.rotate(r),e.textAlign="center",e.textBaseline="middle",H.each(t.ticks,(function(r,l){(0!==l||n.reverse)&&(i=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),n.showLabelBackdrop&&(a=e.measureText(r).width,e.fillStyle=n.backdropColor,e.fillRect(-a/2-n.backdropPaddingX,-i-o.size/2-n.backdropPaddingY,a+2*n.backdropPaddingX,o.size+2*n.backdropPaddingY)),e.fillStyle=s,e.fillText(r,0,-i))})),e.restore()}},_drawTitle:H.noop}),Kn=Vn;Xn._defaults=Kn;var Zn=H._deprecated,$n=H.options.resolve,Jn=H.valueOrDefault,Qn=Number.MIN_SAFE_INTEGER||-9007199254740991,ti=Number.MAX_SAFE_INTEGER||9007199254740991,ei={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ni=Object.keys(ei);function ii(t,e){return t-e}function ai(t){return H.valueOrDefault(t.time.min,t.ticks.min)}function ri(t){return H.valueOrDefault(t.time.max,t.ticks.max)}function oi(t,e,n,i){var a=function(t,e,n){for(var i,a,r,o=0,s=t.length-1;o>=0&&o<=s;){if(a=t[(i=o+s>>1)-1]||null,r=t[i],!a)return{lo:null,hi:r};if(r[e]n))return{lo:a,hi:r};s=i-1}}return{lo:r,hi:null}}(t,e,n),r=a.lo?a.hi?a.lo:t[t.length-2]:t[0],o=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=o[e]-r[e],l=s?(n-r[e])/s:0,u=(o[i]-r[i])*l;return r[i]+u}function si(t,e){var n=t._adapter,i=t.options.time,a=i.parser,r=a||i.format,o=e;return"function"==typeof a&&(o=a(o)),H.isFinite(o)||(o="string"==typeof r?n.parse(o,r):n.parse(o)),null!==o?+o:(a||"function"!=typeof r||(o=r(e),H.isFinite(o)||(o=n.parse(o))),o)}function li(t,e){if(H.isNullOrUndef(e))return null;var n=t.options.time,i=si(t,t.getRightValue(e));return null===i?i:(n.round&&(i=+t._adapter.startOf(i,n.round)),i)}function ui(t,e,n,i){var a,r,o,s=ni.length;for(a=ni.indexOf(t);a=0&&(e[r].major=!0);return e}(t,r,o,n):r}var hi=yn.extend({initialize:function(){this.mergeTicksOptions(),yn.prototype.initialize.call(this)},update:function(){var t=this,e=t.options,n=e.time||(e.time={}),i=t._adapter=new rn._date(e.adapters.date);return Zn("time scale",n.format,"time.format","time.parser"),Zn("time scale",n.min,"time.min","ticks.min"),Zn("time scale",n.max,"time.max","ticks.max"),H.mergeIf(n.displayFormats,i.formats()),yn.prototype.update.apply(t,arguments)},getRightValue:function(t){return t&&void 0!==t.t&&(t=t.t),yn.prototype.getRightValue.call(this,t)},determineDataLimits:function(){var t,e,n,i,a,r,o,s=this,l=s.chart,u=s._adapter,d=s.options,h=d.time.unit||"day",c=ti,f=Qn,g=[],p=[],m=[],v=s._getLabels();for(t=0,n=v.length;t1?function(t){var e,n,i,a={},r=[];for(e=0,n=t.length;e1e5*u)throw e+" and "+n+" are too far apart with stepSize of "+u+" "+l;for(a=h;a=a&&n<=r&&d.push(n);return i.min=a,i.max=r,i._unit=l.unit||(s.autoSkip?ui(l.minUnit,i.min,i.max,h):function(t,e,n,i,a){var r,o;for(r=ni.length-1;r>=ni.indexOf(n);r--)if(o=ni[r],ei[o].common&&t._adapter.diff(a,i,o)>=e-1)return o;return ni[n?ni.indexOf(n):0]}(i,d.length,l.minUnit,i.min,i.max)),i._majorUnit=s.major.enabled&&"year"!==i._unit?function(t){for(var e=ni.indexOf(t)+1,n=ni.length;ee&&s=0&&t0?s:1}}),ci={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};hi._defaults=ci;var fi={category:kn,linear:Tn,logarithmic:zn,radialLinear:Xn,time:hi},gi={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};rn._date.override("function"==typeof t?{_id:"moment",formats:function(){return gi},parse:function(e,n){return"string"==typeof e&&"string"==typeof n?e=t(e,n):e instanceof t||(e=t(e)),e.isValid()?e.valueOf():null},format:function(e,n){return t(e).format(n)},add:function(e,n,i){return t(e).add(n,i).valueOf()},diff:function(e,n,i){return t(e).diff(t(n),i)},startOf:function(e,n,i){return e=t(e),"isoWeek"===n?e.isoWeekday(i).valueOf():e.startOf(n).valueOf()},endOf:function(e,n){return t(e).endOf(n).valueOf()},_create:function(e){return t(e)}}:{}),N._set("global",{plugins:{filler:{propagate:!0}}});var pi={dataset:function(t){var e=t.fill,n=t.chart,i=n.getDatasetMeta(e),a=i&&n.isDatasetVisible(e)&&i.dataset._children||[],r=a.length||0;return r?function(t,e){return e=n)&&i;switch(r){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return r;default:return!1}}function vi(t){return(t.el._scale||{}).getPointPositionForValue?function(t){var e,n,i,a,r,o=t.el._scale,s=o.options,l=o.chart.data.labels.length,u=t.fill,d=[];if(!l)return null;for(e=s.ticks.reverse?o.max:o.min,n=s.ticks.reverse?o.min:o.max,i=o.getPointPositionForValue(0,e),a=0;a0;--r)H.canvas.lineTo(t,n[r],n[r-1],!0);else for(o=n[0].cx,s=n[0].cy,l=Math.sqrt(Math.pow(n[0].x-o,2)+Math.pow(n[0].y-s,2)),r=a-1;r>0;--r)t.arc(o,s,l,n[r].angle,n[r-1].angle,!0)}}function ki(t,e,n,i,a,r){var o,s,l,u,d,h,c,f,g=e.length,p=i.spanGaps,m=[],v=[],b=0,x=0;for(t.beginPath(),o=0,s=g;o=0;--n)(e=l[n].$filler)&&e.visible&&(a=(i=e.el)._view,r=i._children||[],o=e.mapper,s=a.backgroundColor||N.global.defaultColor,o&&s&&r.length&&(H.canvas.clipArea(u,t.chartArea),ki(u,r,o,a,s,i._loop),H.canvas.unclipArea(u)))}},Mi=H.rtl.getRtlAdapter,Si=H.noop,Ci=H.valueOrDefault;function Pi(t,e){return t.usePointStyle&&t.boxWidth>e?e:t.boxWidth}N._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data.datasets,n=t.options.legend||{},i=n.labels&&n.labels.usePointStyle;return t._getSortedDatasetMetas().map((function(n){var a=n.controller.getStyle(i?0:void 0);return{text:e[n.index].label,fillStyle:a.backgroundColor,hidden:!t.isDatasetVisible(n.index),lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,pointStyle:a.pointStyle,rotation:a.rotation,datasetIndex:n.index}}),this)}}},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data.datasets;for(a.setAttribute("class",t.id+"-legend"),e=0,n=r.length;el.width)&&(h+=o+n.padding,d[d.length-(e>0?0:1)]=0),s[e]={left:0,top:0,width:i,height:o},d[d.length-1]+=i+n.padding})),l.height+=h}else{var c=n.padding,f=t.columnWidths=[],g=t.columnHeights=[],p=n.padding,m=0,v=0;H.each(t.legendItems,(function(t,e){var i=Pi(n,o)+o/2+a.measureText(t.text).width;e>0&&v+o+2*c>l.height&&(p+=m+n.padding,f.push(m),g.push(v),m=0,v=0),m=Math.max(m,i),v+=o+c,s[e]={left:0,top:0,width:i,height:o}})),p+=m,f.push(m),g.push(v),l.width+=p}t.width=l.width,t.height=l.height}else t.width=l.width=t.height=l.height=0},afterFit:Si,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,n=e.labels,i=N.global,a=i.defaultColor,r=i.elements.line,o=t.height,s=t.columnHeights,l=t.width,u=t.lineWidths;if(e.display){var d,h=Mi(e.rtl,t.left,t.minSize.width),c=t.ctx,f=Ci(n.fontColor,i.defaultFontColor),g=H.options._parseFont(n),p=g.size;c.textAlign=h.textAlign("left"),c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=g.string;var m=Pi(n,p),v=t.legendHitBoxes,b=function(t,i){switch(e.align){case"start":return n.padding;case"end":return t-i;default:return(t-i+n.padding)/2}},x=t.isHorizontal();d=x?{x:t.left+b(l,u[0]),y:t.top+n.padding,line:0}:{x:t.left+n.padding,y:t.top+b(o,s[0]),line:0},H.rtl.overrideTextDirection(t.ctx,e.textDirection);var y=p+n.padding;H.each(t.legendItems,(function(e,i){var f=c.measureText(e.text).width,g=m+p/2+f,_=d.x,k=d.y;h.setWidth(t.minSize.width),x?i>0&&_+g+n.padding>t.left+t.minSize.width&&(k=d.y+=y,d.line++,_=d.x=t.left+b(l,u[d.line])):i>0&&k+y>t.top+t.minSize.height&&(_=d.x=_+t.columnWidths[d.line]+n.padding,d.line++,k=d.y=t.top+b(o,s[d.line]));var w=h.x(_);!function(t,e,i){if(!(isNaN(m)||m<=0)){c.save();var o=Ci(i.lineWidth,r.borderWidth);if(c.fillStyle=Ci(i.fillStyle,a),c.lineCap=Ci(i.lineCap,r.borderCapStyle),c.lineDashOffset=Ci(i.lineDashOffset,r.borderDashOffset),c.lineJoin=Ci(i.lineJoin,r.borderJoinStyle),c.lineWidth=o,c.strokeStyle=Ci(i.strokeStyle,a),c.setLineDash&&c.setLineDash(Ci(i.lineDash,r.borderDash)),n&&n.usePointStyle){var s=m*Math.SQRT2/2,l=h.xPlus(t,m/2),u=e+p/2;H.canvas.drawPoint(c,i.pointStyle,s,l,u,i.rotation)}else c.fillRect(h.leftForLtr(t,m),e,m,p),0!==o&&c.strokeRect(h.leftForLtr(t,m),e,m,p);c.restore()}}(w,k,e),v[i].left=h.leftForLtr(w,v[i].width),v[i].top=k,function(t,e,n,i){var a=p/2,r=h.xPlus(t,m+a),o=e+a;c.fillText(n.text,r,o),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(r,o),c.lineTo(h.xPlus(r,i),o),c.stroke())}(w,k,e,f),x?d.x+=g+n.padding:d.y+=y})),H.rtl.restoreTextDirection(t.ctx,e.textDirection)}},_getLegendItemAt:function(t,e){var n,i,a,r=this;if(t>=r.left&&t<=r.right&&e>=r.top&&e<=r.bottom)for(a=r.legendHitBoxes,n=0;n=(i=a[n]).left&&t<=i.left+i.width&&e>=i.top&&e<=i.top+i.height)return r.legendItems[n];return null},handleEvent:function(t){var e,n=this,i=n.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!i.onHover&&!i.onLeave)return}else{if("click"!==a)return;if(!i.onClick)return}e=n._getLegendItemAt(t.x,t.y),"click"===a?e&&i.onClick&&i.onClick.call(n,t.native,e):(i.onLeave&&e!==n._hoveredItem&&(n._hoveredItem&&i.onLeave.call(n,t.native,n._hoveredItem),n._hoveredItem=e),i.onHover&&e&&i.onHover.call(n,t.native,e))}});function Di(t,e){var n=new Ai({ctx:t.ctx,options:e,chart:t});pe.configure(t,n,e),pe.addBox(t,n),t.legend=n}var Ti={id:"legend",_element:Ai,beforeInit:function(t){var e=t.options.legend;e&&Di(t,e)},beforeUpdate:function(t){var e=t.options.legend,n=t.legend;e?(H.mergeIf(e,N.global.legend),n?(pe.configure(t,n,e),n.options=e):Di(t,e)):n&&(pe.removeBox(t,n),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}},Ii=H.noop;N._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Fi=K.extend({initialize:function(t){H.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:Ii,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Ii,beforeSetDimensions:Ii,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Ii,beforeBuildLabels:Ii,buildLabels:Ii,afterBuildLabels:Ii,beforeFit:Ii,fit:function(){var t,e=this,n=e.options,i=e.minSize={},a=e.isHorizontal();n.display?(t=(H.isArray(n.text)?n.text.length:1)*H.options._parseFont(n).lineHeight+2*n.padding,e.width=i.width=a?e.maxWidth:t,e.height=i.height=a?t:e.maxHeight):e.width=i.width=e.height=i.height=0},afterFit:Ii,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=t.options;if(n.display){var i,a,r,o=H.options._parseFont(n),s=o.lineHeight,l=s/2+n.padding,u=0,d=t.top,h=t.left,c=t.bottom,f=t.right;e.fillStyle=H.valueOrDefault(n.fontColor,N.global.defaultFontColor),e.font=o.string,t.isHorizontal()?(a=h+(f-h)/2,r=d+l,i=f-h):(a="left"===n.position?h+l:f-l,r=d+(c-d)/2,i=c-d,u=Math.PI*("left"===n.position?-.5:.5)),e.save(),e.translate(a,r),e.rotate(u),e.textAlign="center",e.textBaseline="middle";var g=n.text;if(H.isArray(g))for(var p=0,m=0;m=0;i--){var a=t[i];if(e(a))return a}},H.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},H.almostEquals=function(t,e,n){return Math.abs(t-e)=t},H.max=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.max(t,e)}),Number.NEGATIVE_INFINITY)},H.min=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.min(t,e)}),Number.POSITIVE_INFINITY)},H.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0===(t=+t)||isNaN(t)?t:t>0?1:-1},H.toRadians=function(t){return t*(Math.PI/180)},H.toDegrees=function(t){return t*(180/Math.PI)},H._decimalPlaces=function(t){if(H.isFinite(t)){for(var e=1,n=0;Math.round(t*e)/e!==t;)e*=10,n++;return n}},H.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},H.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},H.aliasPixel=function(t){return t%2==0?0:.5},H._alignPixel=function(t,e,n){var i=t.currentDevicePixelRatio,a=n/2;return Math.round((e-a)*i)/i+a},H.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l),h=i*(u=isNaN(u)?0:u),c=i*(d=isNaN(d)?0:d);return{previous:{x:r.x-h*(o.x-a.x),y:r.y-h*(o.y-a.y)},next:{x:r.x+c*(o.x-a.x),y:r.y+c*(o.y-a.y)}}},H.EPSILON=Number.EPSILON||1e-14,H.splineCurveMonotone=function(t){var e,n,i,a,r,o,s,l,u,d=(t||[]).map((function(t){return{model:t._model,deltaK:0,mK:0}})),h=d.length;for(e=0;e0?d[e-1]:null,(a=e0?d[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},H.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},H.niceNum=function(t,e){var n=Math.floor(H.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},H.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},H.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.target||t.srcElement,o=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var l=parseFloat(H.getStyle(r,"padding-left")),u=parseFloat(H.getStyle(r,"padding-top")),d=parseFloat(H.getStyle(r,"padding-right")),h=parseFloat(H.getStyle(r,"padding-bottom")),c=o.right-o.left-l-d,f=o.bottom-o.top-u-h;return{x:n=Math.round((n-o.left-l)/c*r.width/e.currentDevicePixelRatio),y:i=Math.round((i-o.top-u)/f*r.height/e.currentDevicePixelRatio)}},H.getConstraintWidth=function(t){return n(t,"max-width","clientWidth")},H.getConstraintHeight=function(t){return n(t,"max-height","clientHeight")},H._calculatePadding=function(t,e,n){return(e=H.getStyle(t,e)).indexOf("%")>-1?n*parseInt(e,10)/100:parseInt(e,10)},H._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},H.getMaximumWidth=function(t){var e=H._getParentNode(t);if(!e)return t.clientWidth;var n=e.clientWidth,i=n-H._calculatePadding(e,"padding-left",n)-H._calculatePadding(e,"padding-right",n),a=H.getConstraintWidth(t);return isNaN(a)?i:Math.min(i,a)},H.getMaximumHeight=function(t){var e=H._getParentNode(t);if(!e)return t.clientHeight;var n=e.clientHeight,i=n-H._calculatePadding(e,"padding-top",n)-H._calculatePadding(e,"padding-bottom",n),a=H.getConstraintHeight(t);return isNaN(a)?i:Math.min(i,a)},H.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},H.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,r=t.width;i.height=a*n,i.width=r*n,t.ctx.scale(n,n),i.style.height||i.style.width||(i.style.height=a+"px",i.style.width=r+"px")}},H.fontString=function(t,e,n){return e+" "+t+"px "+n},H.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var o,s,l,u,d,h=0,c=n.length;for(o=0;on.length){for(o=0;oi&&(i=r),i},H.numberOfLabelLines=function(t){var e=1;return H.each(t,(function(t){H.isArray(t)&&t.length>e&&(e=t.length)})),e},H.color=_?function(t){return t instanceof CanvasGradient&&(t=N.global.defaultColor),_(t)}:function(t){return console.error("Color.js not found!"),t},H.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:H.color(t).saturate(.5).darken(.1).rgbString()}}(),en._adapters=rn,en.Animation=$,en.animationService=J,en.controllers=Jt,en.DatasetController=it,en.defaults=N,en.Element=K,en.elements=kt,en.Interaction=re,en.layouts=pe,en.platform=Oe,en.plugins=Le,en.Scale=yn,en.scaleService=Re,en.Ticks=on,en.Tooltip=Ye,en.helpers.each(fi,(function(t,e){en.scaleService.registerScaleType(e,t,t._defaults)})),Li)Li.hasOwnProperty(Bi)&&en.plugins.register(Li[Bi]);en.platform.initialize();var Ei=en;return"undefined"!=typeof window&&(window.Chart=en),en.Chart=en,en.Legend=Li.legend._element,en.Title=Li.title._element,en.pluginService=en.plugins,en.PluginBase=en.Element.extend({}),en.canvasHelpers=en.helpers.canvas,en.layoutService=en.layouts,en.LinearScaleBase=Cn,en.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],(function(t){en[t]=function(e,n){return new en(e,en.helpers.merge(n||{},{type:t.charAt(0).toLowerCase()+t.slice(1)}))}})),Ei})); diff --git a/docs/user/language-bindings/android/android-build-configuration-options.md b/docs/user/language-bindings/android/android-build-configuration-options.md index d19b1502c5..add0ea1234 100644 --- a/docs/user/language-bindings/android/android-build-configuration-options.md +++ b/docs/user/language-bindings/android/android-build-configuration-options.md @@ -32,7 +32,7 @@ The Glean SDK can automatically generate Markdown documentation for metrics and ext.gleanGenerateMarkdownDocs = true ``` -Flipping the feature to `true` will generate a `metrics.md` file in `$projectDir/docs` at build-time. +Flipping the feature to `true` will generate a `metrics.md` file in `$projectDir/docs` at build-time. In general this is not necessary for projects using Mozilla's data ingestion infrastructure: in those cases human-readable documentation will automatically be viewable via the [Glean Dictionary](https://dictionary.telemetry.mozilla.org). ## `gleanDocsDirectory` diff --git a/docs/user/language-bindings/index.md b/docs/user/language-bindings/index.md new file mode 100644 index 0000000000..36cc4795fa --- /dev/null +++ b/docs/user/language-bindings/index.md @@ -0,0 +1,53 @@ +# Overview + +The Glean SDK provides multiple language bindings for integration into different platforms and with different programming languages + +## Rust + +The Rust bindings can be used with any Rust application. +It can serve a wide variety of usage patterns, +such as short-lived CLI applications as well as longer-running desktop or server applications. +It can optionally send builtin pings at startup. +It does not assume an interaction model and the integrating application is responsible to connect the respective hooks. + +It is available as the [`glean` crate][glean crate] on crates.io. + +[glean crate]: https://crates.io/crates/glean + +## Kotlin + +The Kotlin bindings are primarily used for integration with Android applications. +It assumes a common interaction model for mobile applications. +It sends builtin pings at startup of the integrating application. + +It is available standalone as `org.mozilla.telemetry:glean` +or via [Android Components][ac] as `org.mozilla.components:service-glean` +from the [Mozilla Maven instance][maven]. + +> *Note*: The Kotlin bindings can also be used from Java. + +[ac]: https://github.com/mozilla-mobile/android-components/ +[maven]: https://maven.mozilla.org/?prefix=maven2 + +See [Android](android/) for more on integrating Glean on Android. + +## Swift + +The Swift bindings are primarily used for integration with iOS applications. +It assumes a common interaction model for mobile applications. +It sends builtin pings at startup of the integrating application. + +It is available as a standalone Xcode framework from the [Glean releases page][releases] or bundled with the [AppServices framework][as-releases]. + +[releases]: https://github.com/mozilla/glean/releases +[as-releases]: https://github.com/mozilla/application-services/releases + +## Python + +The Python bindings allow integration with any Python application. +It can serve a wide variety of usage patterns, +such as short-lived CLI applications as well as longer-running desktop or server applications. + +It is available as [`glean-sdk` on PyPI][pypi]. + +[pypi]: https://pypi.org/project/glean-sdk/ diff --git a/docs/user/reference/general/index.md b/docs/user/reference/general/index.md index 38cfaf80da..44967fb5e7 100644 --- a/docs/user/reference/general/index.md +++ b/docs/user/reference/general/index.md @@ -26,7 +26,11 @@ The Glean SDK provides a general API that supports the following operations. See The following steps are required for applications using the Glean SDK, but not libraries. -> **Note**: The `initialize` function _must_ be called, even if telemetry upload is disabled. +{{#include ../../../shared/blockquote-info.html}} + +##### Note + +> The `initialize` function _must_ be called, even if telemetry upload is disabled. > Glean needs to perform maintenance tasks even when telemetry is disabled, and because Glean > does this as part of its initialization, it is _required_ to always call the `initialize` > function. Otherwise, Glean won't be able to clean up collected data, disable queuing of pre-init @@ -34,6 +38,15 @@ The following steps are required for applications using the Glean SDK, but not l > > This does not apply to special builds where telemetry is disabled at build time. In that case, it is acceptable to not call `initialize` at all. +{{#include ../../../shared/blockquote-stop.html}} + +##### Initialize clean with the correct value for `uploadEnabled`! + +> `Glean.initialize` must **always** be called with real values. +> Always pass the user preference, e.g. `Glean.initialize(upload=userSettings.telemetry_enabled)` or the equivalent for your application. +> Never call `Glean.initialize(upload=true)` if `true` is a placeholder value that later gets reset by `Glean.setUploadEnabled(false)`. +> Depending on the provided placeholder value, this might trigger the generation of new client ids or the submission of bogus [`deletion-request` pings](../../user/pings/deletion_request.md). + {{#include ../../../shared/tab_header.md}}
diff --git a/docs/user/reference/metrics/boolean.md b/docs/user/reference/metrics/boolean.md index 2c0a76361b..5ff52913bb 100644 --- a/docs/user/reference/metrics/boolean.md +++ b/docs/user/reference/metrics/boolean.md @@ -1,22 +1,12 @@ # Boolean -Booleans are used for simple flags, for example "is a11y enabled"?. +Boolean metrics are used for reporting simple flags. -## Configuration +## Recording API -Say you're adding a boolean to record whether a11y is enabled on the device. First you need to add an entry for the boolean to the `metrics.yaml` file: +### `set` -```YAML -flags: - a11y_enabled: - type: boolean - description: > - Records whether a11y is enabled on the device. - lifetime: application - ... -``` - -## API +Sets a boolean metric to a specific value. {{#include ../../../shared/tab_header.md}} @@ -28,17 +18,6 @@ import org.mozilla.yourApplication.GleanMetrics.Flags Flags.a11yEnabled.set(System.isAccesibilityEnabled()) ``` -There are test APIs available too: - -```Kotlin -import org.mozilla.yourApplication.GleanMetrics.Flags - -// Was anything recorded? -assertTrue(Flags.a11yEnabled.testHasValue()) -// Does it have the expected value? -assertTrue(Flags.a11yEnabled.testGetValue()) -``` -
@@ -49,14 +28,94 @@ import org.mozilla.yourApplication.GleanMetrics.Flags; Flags.INSTANCE.a11yEnabled.set(System.isAccessibilityEnabled()); ``` -There are test APIs available too: +
+ + +
+ +```Swift +Flags.a11yEnabled.set(self.isAccessibilityEnabled) +``` + +
+ +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +metrics.flags.a11y_enabled.set(is_accessibility_enabled()) +``` + +
+ +
+ +``` +use glean_metrics; + +flags::a11y_enabled.set(system.is_accessibility_enabled()); +``` + +
+ +
+ +```js +import * as flags from "./path/to/generated/files/flags.js"; + +flags.a11yEnabled.set(this.isAccessibilityEnabled()); +``` +
+ +
+ +**C++** + +```cpp +#include "mozilla/glean/GleanMetrics.h" + +mozilla::glean::flags::a11y_enabled.Set(false); +``` + +**Javascript** + +```js +Glean.flags.a11yEnabled.set(false); +``` + +
+ +{{#include ../../../shared/tab_footer.md}} + +#### Recorded errors + +N/A + +## Testing API + +### `testGetValue` + +Gets the recorded value for a given boolean metric. + +{{#include ../../../shared/tab_header.md}} + +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Flags + +assertTrue(Flags.a11yEnabled.testGetValue()) +``` + +
+ +
```Java import org.mozilla.yourApplication.GleanMetrics.Flags; -// Was anything recorded? -assertTrue(Flags.INSTANCE.a11yEnabled.testHasValue()); -// Does it have the expected value? assertTrue(Flags.INSTANCE.a11yEnabled.testGetValue()); ``` @@ -65,18 +124,9 @@ assertTrue(Flags.INSTANCE.a11yEnabled.testGetValue());
-```Swift -Flags.a11yEnabled.set(self.isAccessibilityEnabled) -``` - -There are test APIs available too: - ```Swift @testable import Glean -// Was anything recorded? -XCTAssertTrue(Flags.a11yEnabled.testHasValue()) -// Does the counter have the expected value? XCTAssertTrue(try Flags.a11yEnabled.testGetValue()) ``` @@ -88,112 +138,135 @@ XCTAssertTrue(try Flags.a11yEnabled.testGetValue()) from glean import load_metrics metrics = load_metrics("metrics.yaml") -metrics.flags.a11y_enabled.set(is_accessibility_enabled()) -``` - -There are test APIs available too: - -```Python -# Was anything recorded? -assert metrics.flags.a11y_enabled.test_has_value() -# Does it have the expected value? assert True is metrics.flags.a11y_enabled.test_get_value() ```
-
+
-```C# -using static Mozilla.YourApplication.GleanMetrics.FlagsOuter; +``` +use glean_metrics; -Flags.a11yEnabled.Set(System.IsAccessibilityEnabled()); +assert!(flags::a11y_enabled.test_get_value(None).unwrap()); ``` -There are test APIs available too: +
-```C# -using static Mozilla.YourApplication.GleanMetrics.FlagsOuter; +
-// Was anything recorded? -Assert.True(Flags.a11yEnabled.TestHasValue()); -// Does it have the expected value? -Assert.True(Flags.a11yEnabled.TestGetValue()); -``` +```js +import * as flags from "./path/to/generated/files/flags.js"; +assert(await flags.a11yEnabled.testGetValue()); +```
-
+
-```rust -use glean_metrics; +**C++** -flags::a11y_enabled.set(system.is_accessibility_enabled()); -``` +```cpp +#include "mozilla/glean/GleanMetrics.h" -There are test APIs available too: +ASSERT_EQ(false, mozilla::glean::flags::a11y_enabled.TestGetValue().value()); +``` -```rust -use glean_metrics; +**Javascript** -// Was anything recorded? -assert!(flags::a11y_enabled.test_get_value(None).is_some()); -// Does it have the expected value? -assert!(flags::a11y_enabled.test_get_value(None).unwrap()); +```js +Assert.equal(false, Glean.flags.a11yEnabled.testGetValue()); ```
-
+{{#include ../../../shared/tab_footer.md}} -> **Note**: C++ APIs are only available in Firefox Desktop. +### `testHasValue` -```c++ -#include "mozilla/glean/GleanMetrics.h" +Whether or not **any** value was recorded for a given boolean metric. -mozilla::glean::flags::a11y_enabled.Set(false); +{{#include ../../../shared/tab_header.md}} + +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Flags + +assertTrue(Flags.a11yEnabled.testHasValue()) ``` -There are test APIs available too: +
-```c++ -#include "mozilla/glean/GleanMetrics.h" +
-ASSERT_EQ(false, mozilla::glean::flags::a11y_enabled.TestGetValue().value()); +```Java +import org.mozilla.yourApplication.GleanMetrics.Flags; + +assertTrue(Flags.INSTANCE.a11yEnabled.testHasValue()); ```
-
-> **Note**: JS APIs are currently only available in Firefox Desktop. -> General JavaScript support is coming soon via [the Glean.js project](https://github.com/mozilla/glean.js/). +
+ +```Swift +@testable import Glean -```js -Glean.flags.a11yEnabled.set(false); +XCTAssertTrue(try Flags.a11yEnabled.testHasValue()) ``` -There are test APIs available too: +
-```js -Assert.equal(false, Glean.flags.a11yEnabled.testGetValue()); +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +assert True is metrics.flags.a11y_enabled.test_has_value() ```
+
+ +
+ +
+ {{#include ../../../shared/tab_footer.md}} -## Limits +## Metric parameters + +Example boolean metric definition: + +```yaml +flags: + a11y_enabled: + type: boolean + description: > + Records whether a11y is enabled on the device. + bugs: + - https://bugzilla.mozilla.org/000000 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3 + notification_emails: + - me@mozilla.com + expires: 2020-10-01 +``` -* None. +For a full reference on metrics parameters common to all metric types, +refer to the metrics [YAML format](../yaml/index.md) reference page. -## Examples +### Extra metric parameters -* Is a11y enabled? +N/A -## Recorded errors +## Data questions -* None. +* Is accessibility enabled? ## Reference diff --git a/docs/user/reference/metrics/counter.md b/docs/user/reference/metrics/counter.md index 84d6f352da..e04e56b467 100644 --- a/docs/user/reference/metrics/counter.md +++ b/docs/user/reference/metrics/counter.md @@ -3,51 +3,124 @@ Used to count how often something happens, say how often a certain button was pressed. A counter always starts from `0`. Each time you record to a counter, its value is incremented. -Unless incremented by a positive value, a counter will not be reported in pings. +Unless incremented by a positive value, a counter will not be reported in pings, +that means: the value `0` is never sent in a ping. -> **IMPORTANT:** When using a counter metric, it is important to let the Glean metric do the counting. Using your own variable for counting and setting the counter yourself could be problematic because it will be difficult to reset the value at the exact moment that the value is sent in a ping. Instead, just use `counter.add` to increment the value and let Glean handle resetting the counter. +If you find that you need to control the actual value sent in the ping, you may be measuring something, +not just counting something, and a [Quantity metric](quantity.html) may be a better choice. -If you find that you need to control the actual value sent in the ping, you may be measuring something, not just counting something, and a [Quantity metric](quantity.html) may be a better choice. +{{#include ../../../shared/blockquote-warning.html}} -## Configuration +## Let the Glean metric do the counting -Say you're adding a new counter for how often the refresh button is pressed. First you need to add an entry for the counter to the `metrics.yaml` file: +> When using a counter metric, it is important to let the Glean metric do the counting. +> Using your own variable for counting and setting the counter yourself could be problematic because +> it will be difficult to reset the value at the exact moment that the value is sent in a ping. +> Instead, just use `counter.add` to increment the value and let Glean handle resetting the counter. -```YAML -controls: - refresh_pressed: - type: counter - description: > - Counts how often the refresh button is pressed. - ... -``` +## Recording API -## API +### `add` -{{#include ../../../shared/tab_header.md}} +Increases the counter by a certain amount. If no amount is passed it defaults to `1`. +{{#include ../../../shared/tab_header.md}}
-```Kotlin -import org.mozilla.yourApplication.GleanMetrics.Controls + ```Kotlin + import org.mozilla.yourApplication.GleanMetrics.Controls -Controls.refreshPressed.add() // Adds 1 to the counter. -Controls.refreshPressed.add(5) // Adds 5 to the counter. -``` + Controls.refreshPressed.add() // Adds 1 to the counter. + Controls.refreshPressed.add(5) // Adds 5 to the counter. + ``` +
+
+ + ```Java + import org.mozilla.yourApplication.GleanMetrics.Controls; + + Controls.INSTANCE.refreshPressed.add(); // Adds 1 to the counter. + Controls.INSTANCE.refreshPressed.add(5); // Adds 5 to the counter. + ``` +
+
+ + ```Swift + Controls.refreshPressed.add() // Adds 1 to the counter. + Controls.refreshPressed.add(5) // Adds 5 to the counter. + ``` +
+
+ + ```Python + from glean import load_metrics + metrics = load_metrics("metrics.yaml") + + metrics.controls.refresh_pressed.add() # Adds 1 to the counter. + metrics.controls.refresh_pressed.add(5) # Adds 5 to the counter. + ``` +
+
+ + ```Rust + use glean_metrics; + + controls::refresh_pressed.add(1); // Adds 1 to the counter. + controls::refresh_pressed.add(5); // Adds 5 to the counter. + ``` +
+
-There are test APIs available too: + ```js + import * as controls from "./path/to/generated/files/controls.js"; + + controls.refreshPressed.add(); // Adds 1 to the counter. + controls.refreshPressed.add(5); // Adds 5 to the counter. + ``` +
+
+ + **C++** + + ```cpp + #include "mozilla/glean/GleanMetrics.h" + + mozila::glean::controls::refresh_pressed.Add(1); + mozilla::glean::controls::refresh_pressed.Add(5); + ``` + + **Javascript** + + ```js + Glean.controls.refreshPressed.add(1); + Glean.controls.refreshPressed.add(5); + ``` +
+{{#include ../../../shared/tab_footer.md}} + +#### Recorded errors + +* [`invalid_value`](../../user/metrics/error-reporting.md): If the counter is incremented by `0` or a negative value. + +#### Limits + +* Only increments; +* Saturates at the largest value that can be represented as a 32-bit signed integer (`2147483647`). + +## Testing API + +### `testGetValue` + +Gets the recorded value for a given counter metric. + +{{#include ../../../shared/tab_header.md}} + +
```Kotlin import org.mozilla.yourApplication.GleanMetrics.Controls -// Was anything recorded? -assertTrue(Controls.refreshPressed.testHasValue()) -// Does the counter have the expected value? assertEquals(6, Controls.refreshPressed.testGetValue()) -// Did the counter record a negative value? -assertEquals( - 1, Controls.refreshPressed.testGetNumRecordedErrors(ErrorType.InvalidValue) -) ```
@@ -57,45 +130,18 @@ assertEquals( ```Java import org.mozilla.yourApplication.GleanMetrics.Controls; -Controls.INSTANCE.refreshPressed.add(); // Adds 1 to the counter. -Controls.INSTANCE.refreshPressed.add(5); // Adds 5 to the counter. -``` - -There are test APIs available too: - -```Java -import org.mozilla.yourApplication.GleanMetrics.Controls; - -// Was anything recorded? -assertTrue(Controls.INSTANCE.refreshPressed.testHasValue()); -// Does the counter have the expected value? assertEquals(6, Controls.INSTANCE.refreshPressed.testGetValue()); -// Did the counter record a negative value? -assertEquals( - 1, Controls.INSTANCE.refreshPressed.testGetNumRecordedErrors(ErrorType.InvalidValue) -); ```
-
-```Swift -Controls.refreshPressed.add() // Adds 1 to the counter. -Controls.refreshPressed.add(5) // Adds 5 to the counter. -``` - -There are test APIs available too: +
```Swift @testable import Glean -// Was anything recorded? -XCTAssert(Controls.refreshPressed.testHasValue()) -// Does the counter have the expected value? XCTAssertEqual(6, try Controls.refreshPressed.testGetValue()) -// Did the counter record a negative value? -XCTAssertEqual(1, Controls.refreshPressed.testGetNumRecordedErrors(.invalidValue)) ```
@@ -106,138 +152,211 @@ XCTAssertEqual(1, Controls.refreshPressed.testGetNumRecordedErrors(.invalidValue from glean import load_metrics metrics = load_metrics("metrics.yaml") -metrics.controls.refresh_pressed.add() # Adds 1 to the counter. -metrics.controls.refresh_pressed.add(5) # Adds 5 to the counter. +assert 6 == metrics.controls.refresh_pressed.test_get_value() ``` -There are test APIs available too: +
-```Python -# Was anything recorded? -assert metrics.controls.refresh_pressed.test_has_value() -# Does the counter have the expected value? -assert 6 == metrics.controls.refresh_pressed.test_get_value() -# Did the counter record a negative value? -from glean.testing import ErrorType -assert 1 == metrics.controls.refresh_pressed.test_get_num_recorded_errors( - ErrorType.INVALID_VALUE -) +
+ +```rust +use glean_metrics; + +assert_eq!(6, controls::refresh_pressed.test_get_value(None).unwrap()); ```
-
+
+ + ```js + import * as controls from "./path/to/generated/files/controls.js"; + + assert.strictEqual(6, await controls.refreshPressed.testGetValue()); + ``` +
+ +
+ +**C++** + +```cpp +#include "mozilla/glean/GleanMetrics.h" + +ASSERT_EQ(6, mozilla::glean::controls::refresh_pressed.TestGetValue().value()); +``` -```C# -using static Mozilla.YourApplication.GleanMetrics.Controls; +**Javascript** -Controls.refreshPressed.Add(); // Adds 1 to the counter. -Controls.refreshPressed.Add(5); // Adds 5 to the counter. +```js +Assert.equal(6, Glean.controls.refreshPressed.testGetValue()); ``` -There are test APIs available too: +
-```C# -using static Mozilla.YourApplication.GleanMetrics.Controls; +{{#include ../../../shared/tab_footer.md}} -// Was anything recorded? -Assert.True(Controls.refreshPressed.TestHasValue()); -// Does the counter have the expected value? -Assert.Equal(6, Controls.refreshPressed.TestGetValue()); -// Did the counter record a negative value? -Assert.Equal( - 1, Controls.refreshPressed.TestGetNumRecordedErrors(ErrorType.InvalidValue) -); +### `testHasValue` + +Whether or not **any** value was recorded for a given counter metric. + +{{#include ../../../shared/tab_header.md}} + +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Controls + +assertTrue(Controls.refreshPressed.testHasValue()) ```
-
+
-```rust -use glean_metrics; +```Java +import org.mozilla.yourApplication.GleanMetrics.Controls; -controls::refresh_pressed.add(1); // Adds 1 to the counter. -controls::refresh_pressed.add(5); // Adds 5 to the counter. +assertTrue(Controls.INSTANCE.refreshPressed.testHasValue()); ``` -There are test APIs available too: +
-```rust -use glean::ErrorType; -use glean_metrics; +
-// Was anything recorded? -assert!(controls::refresh_pressed.test_get_value(None).is_some()); -// Does the counter have the expected value? -assert_eq!(6, controls::refresh_pressed.test_get_value(None).unwrap()); -// Did the counter record an negative value? -assert_eq!( - 1, - controls::refresh_pressed.test_get_num_recorded_errors( - ErrorType::InvalidValue - ) -); +```Swift +@testable import Glean + +XCTAssert(Controls.refreshPressed.testHasValue()) ```
-
+
-> **Note**: C++ APIs are only available in Firefox Desktop. +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") -```c++ -#include "mozilla/glean/GleanMetrics.h" +assert metrics.controls.refresh_pressed.test_has_value() +``` + +
+ +
+ +
+ +
+ +{{#include ../../../shared/tab_footer.md}} + +### `testGetNumRecordedErrors` -mozilla::glean::controls::refresh_pressed.Add(1); -mozilla::glean::controls::refresh_pressed.Add(5); +Gets number of errors recorded for a given counter metric. + +{{#include ../../../shared/tab_header.md}} + +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Controls + +assertEquals( + 1, Controls.refreshPressed.testGetNumRecordedErrors(ErrorType.InvalidValue) +) ``` -There are test APIs available too: +
-```c++ -#include "mozilla/glean/GleanMetrics.h" +
-// Does the counter have the expected value? -ASSERT_EQ(6, mozilla::glean::controls::refresh_pressed.TestGetValue().value()); -// Did it run across any errors? -// TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1683171 +```Java +import org.mozilla.yourApplication.GleanMetrics.Controls; + +assertEquals( + 1, Controls.INSTANCE.refreshPressed.testGetNumRecordedErrors(ErrorType.InvalidValue) +); ```
-
-> **Note**: JS APIs are currently only available in Firefox Desktop. -> General JavaScript support is coming soon via [the Glean.js project](https://github.com/mozilla/glean.js/). +
-```js -Glean.controls.refreshPressed.add(1); -Glean.controls.refreshPressed.add(5); +```Swift +XCTAssertEqual(1, Controls.refreshPressed.testGetNumRecordedErrors(.invalidValue)) ``` -There are test APIs available too: +
+ +
-```js -Assert.equal(6, Glean.controls.refreshPressed.testGetValue()); +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +from glean.testing import ErrorType + +assert 1 == metrics.controls.refresh_pressed.test_get_num_recorded_errors( + ErrorType.INVALID_VALUE +) +``` +
+ +
+ +```rust +use glean::ErrorType; + +use glean_metrics; + +assert_eq!( + 1, + controls::refresh_pressed.test_get_num_recorded_errors( + ErrorType::InvalidValue + ) +); ```
+
+ +
+ {{#include ../../../shared/tab_footer.md}} -## Limits +## Metric parameters + +Example counter metric definition: + +```yaml +controls: + refresh_pressed: + type: counter + description: > + Counts how often the refresh button is pressed. + bugs: + - https://bugzilla.mozilla.org/000000 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3 + notification_emails: + - me@mozilla.com + expires: 2020-10-01 +``` -* Only increments, saturates at the largest value that can be represented as a 32-bit signed integer (`2147483647`). +For a full reference on metrics parameters common to all metric types, +refer to the metrics [YAML format](../yaml/index.md) reference page. -## Examples +### Extra metric parameters -* How often was a certain button was pressed? +N/A -## Recorded errors +## Data questions -* `invalid_value`: If the counter is incremented by `0` or a negative value. +* How often was a certain button pressed? ## Reference diff --git a/docs/user/reference/metrics/datetime.md b/docs/user/reference/metrics/datetime.md index bcee371111..bffc118ab5 100644 --- a/docs/user/reference/metrics/datetime.md +++ b/docs/user/reference/metrics/datetime.md @@ -1,39 +1,18 @@ # Datetime -Datetimes are used to record an absolute date and time, for example the date and time that the application was first run. +Datetimes are used to record an absolute date and time, +for example the date and time that the application was first run. The device's offset from UTC is recorded and sent with the Datetime value in the ping. -To record a single elapsed time, see [Timespan](timespan.md). To measure the distribution of multiple timespans, see [Timing Distributions](timing_distribution.md). +To record a single elapsed time, see [Timespan](timespan.md). +To measure the distribution of multiple timespans, see [Timing Distributions](timing_distribution.md). -## Configuration +## Recording API -Datetimes have a required `time_unit` parameter to specify the smallest unit of resolution that the metric will record. The allowed values for `time_unit` are: - - - `nanosecond` - - `microsecond` - - `millisecond` - - `second` - - `minute` - - `hour` - - `day` - -Carefully consider the required resolution for recording your metric, and choose the coarsest resolution possible. - -You first need to add an entry for it to the `metrics.yaml` file: - -```YAML -install: - first_run: - type: datetime - time_unit: day - description: > - Records the date when the application was first run - lifetime: user - ... -``` +### `set` -## API +Sets a datetime metric to a specific date value. Defaults to now. {{#include ../../../shared/tab_header.md}} @@ -46,21 +25,6 @@ Install.firstRun.set() // Records "now" Install.firstRun.set(Calendar(2019, 3, 25)) // Records a custom datetime ``` -There are test APIs available too. - -```Kotlin -import org.mozilla.yourApplication.GleanMetrics.Install - -// Was anything recorded? -assertTrue(Install.firstRun.testHasValue()) -// Was it the expected value? -// NOTE: Datetimes always include a timezone offset from UTC, hence the -// "-05:00" suffix. -assertEquals("2019-03-25-05:00", Install.firstRun.testGetValueAsString()) -// Was the value invalid? -assertEquals(0, Install.firstRun.testGetNumRecordedErrors(ErrorType.InvalidValue)) -``` -
@@ -72,48 +36,18 @@ Install.INSTANCE.firstRun.set(); // Records "now" Install.INSTANCE.firstRun.set(Calendar(2019, 3, 25)); // Records a custom datetime ``` -There are test APIs available too: - -```Java -import org.mozilla.yourApplication.GleanMetrics.Install; - -// Was anything recorded? -assertTrue(Install.INSTANCE.firstRun.testHasValue()); -// Was it the expected value? -// NOTE: Datetimes always include a timezone offset from UTC, hence the -// "-05:00" suffix. -assertEquals("2019-03-25-05:00", Install.INSTANCE.firstRun.testGetValueAsString()); -// Was the value invalid? -assertEquals(0, Install.INSTANCE.firstRun.testGetNumRecordedErrors(ErrorType.InvalidValue)); -``` -
```Swift Install.firstRun.set() // Records "now" - let dateComponents = DateComponents( calendar: Calendar.current, year: 2004, month: 12, day: 9, hour: 8, minute: 3, second: 29 ) Install.firstRun.set(dateComponents.date!) // Records a custom datetime ``` - -There are test APIs available too: - -```Swift -@testable import Glean - -// Was anything recorded? -XCTAssert(Install.firstRun.testHasValue()) -// Does the datetime have the expected value? -XCTAssertEqual(6, try Install.firstRun.testGetValue()) -// Was the value invalid? -XCTAssertEqual(0, Install.firstRun.getNumRecordedErrors(.invalidValue)) -``` -
@@ -124,149 +58,389 @@ import datetime from glean import load_metrics metrics = load_metrics("metrics.yaml") -# Records "now" -metrics.install.first_run.set() -# Records a custom datetime -metrics.install.first_run.set(datetime.datetime(2019, 3, 25)) +metrics.install.first_run.set() # Records "now" +metrics.install.first_run.set(datetime.datetime(2019, 3, 25)) # Records a custom datetime ``` -There are test APIs available too. +
-```Python -# Was anything recorded? -assert metrics.install.first_run.test_has_value() +
-# Was it the expected value? -# NOTE: Datetimes always include a timezone offset from UTC, hence the -# "-05:00" suffix. -assert "2019-03-25-05:00" == metrics.install.first_run.test_get_value_as_str() -# Was the value invalid? -assert 0 == metrics.install.test_get_num_recorded_errors( - ErrorType.INVALID_VALUE -) +```Rust +use glean_metrics; + +use chrono::{FixedOffset, TimeZone}; + +install::first_run.set(None); // Records "now" +let custom_date = FixedOffset::east(0).ymd(2019, 3, 25).and_hms(0, 0, 0); +install::first_run.set(Some(custom_date)); // Records a custom datetime ```
-
+
-```C# -using static Mozilla.YourApplication.GleanMetrics.Install; +```js +import * as install from "./path/to/generated/files/install.js"; -// Records "now" -Install.firstRun.Set(); -// Records a custom datetime -Install.firstRun.Set(new DateTimeOffset(2018, 2, 25, 11, 10, 0, TimeZone.CurrentTimeZone.BaseUtcOffset)); +install.firstRun.set(); // Records "now" +install.firstRun.set(new Date("March 25, 2019 00:00:00")); // Records a custom datetime ``` +
+ +
-There are test APIs available too: +**C++** -```C# -using static Mozilla.YourApplication.GleanMetrics.Install; +```c++ +#include "mozilla/glean/GleanMetrics.h" + +PRExplodedTime date = {0, 35, 10, 12, 6, 10, 2020, 0, 0, {5 * 60 * 60, 0}}; +mozilla::glean::install::first_run.Set(&date); +``` + +**Javascript** -// Was anything recorded? -Assert.True(Install.firstRun.TestHasValue()); -// Was it the expected value? -// NOTE: Datetimes always include a timezone offset from UTC, hence the -// "-05:00" suffix. -Assert.Equal("2019-03-25-05:00", Install.firstRun.TestGetValueAsString()); -// Was the value invalid? -Assert.Equal(0, Install.firstRun.TestGetNumRecordedErrors(ErrorType.InvalidValue)); +```js +const value = new Date("2020-06-11T12:00:00"); +Glean.install.firstRun.set(value.getTime() * 1000); ```
-
+{{#include ../../../shared/tab_footer.md}} -```Rust -use glean::ErrorType; -use glean_metrics; +#### Recorded errors -use chrono::{FixedOffset, TimeZone}; +* [`invalid_value`](../../user/metrics/error-reporting.md): Setting the date time to an invalid value. -// Records "now" -install::first_run.set(None); -// Records a custom datetime -let custom_date = FixedOffset::east(0).ymd(2019, 3, 25).and_hms(0, 0, 0); -install::first_run.set(Some(custom_date)); +## Testing API + +### `testGetValue` + +Get the recorded value for a given datetime metric as a language-specific Date object. + +{{#include ../../../shared/tab_header.md}} + +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Install + +assertEquals(Install.firstRun.testGetValue(), Date(2019, 3, 25)) +``` +
+ +
+ +```Java +import org.mozilla.yourApplication.GleanMetrics.Install; + +assertEquals(Install.INSTANCE.firstRun.testGetValue(), Date(2019, 3, 25)); ``` +
-There are test APIs available too. +
+ +```Swift +@testable import Glean + +let expectedDate = DateComponents( + calendar: Calendar.current, + year: 2004, month: 12, day: 9, hour: 8, minute: 3, second: 29 + ) +XCTAssertEqual(expectedDate.date!, try Install.firstRun.testGetValue()) +``` +
+ +
+ +```Python +import datetime + +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +value = datetime.datetime(1993, 2, 23, 5, 43, tzinfo=datetime.timezone.utc) +assert value == metrics.install.first_run.test_get_value() +``` +
+ +
```Rust -// Was anything recorded? -assert!(metrics.install.first_run.test_get_value(None).is_some()); +use glean_metrics; + +use chrono::{FixedOffset, TimeZone}; -// Was it the expected value? let expected_date = FixedOffset::east(0).ymd(2019, 3, 25).and_hms(0, 0, 0); assert_eq!(expected_date, metrics.install.first_run.test_get_value(None)); -// Was the value invalid? -assert_eq!(0, install::first_run.test_get_num_recorded_errors( - ErrorType::InvalidValue -)); ```
-
- -> **Note**: C++ APIs are only available in Firefox Desktop. +
-```c++ -#include "mozilla/glean/GleanMetrics.h" +```js +import * as install from "./path/to/generated/files/install.js"; -PRExplodedTime date = {0, 35, 10, 12, 6, 10, 2020, 0, 0, {5 * 60 * 60, 0}}; -mozilla::glean::install::first_run.Set(&date); +const expectedDate = new Date("March 25, 2019 00:00:00"); +assert.deepStrictEqual(expectedDate, await install.firstRun.testGetValue()); ``` +
+ +
-There are test APIs available too: +**C++** ```c++ #include "mozilla/glean/GleanMetrics.h" -// Does it have the expected value? ASSERT_STREQ( mozilla::glean::install::first_run.TestGetValue().value(), "2020-11-06T12:10:35+05:00"_ns ); -// Did it run across any errors? -// TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1683171 +``` + +**Javascript** + +```js +Assert.ok(Glean.install.firstRun.testGetValue().startsWith("2020-06-11T12:00:00")); ```
+{{#include ../../../shared/tab_footer.md}} -
+### `testGetValueAsString` -> **Note**: JS APIs are only available in Firefox Desktop. +Get the recorded value for a given datetime metric as an [ISO Date String](https://en.wikipedia.org/wiki/ISO_8601#Dates). -```js -const value = new Date("2020-06-11T12:00:00"); -Glean.install.firstRun.set(value.getTime() * 1000); +The returned string will be truncated to the metric's [time unit](#time_unit) +and will include the timezone offset from UTC, e.g. `2019-03-25-05:00` +(in this example, `time_unit` is `day`). + +{{#include ../../../shared/tab_header.md}} + +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Install + +assertEquals("2019-03-25-05:00", Install.firstRun.testGetValueAsString()) ``` -There are test APIs available too: +
+ +
+ +```Java +import org.mozilla.yourApplication.GleanMetrics.Install; + +assertEquals("2019-03-25-05:00", Install.INSTANCE.firstRun.testGetValueAsString()); +``` + +
+ +
+ +```Swift +@testable import Glean + +assertEquals("2019-03-25-05:00", try Install.firstRun.testGetValueAsString()) +``` +
+ +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +assert "2019-03-25-05:00" == metrics.install.first_run.test_get_value_as_str() +``` + +
+ +
+ +
```js -Assert.ok(Glean.install.firstRun.testGetValue().startsWith("2020-06-11T12:00:00")); -// Did it run across any errors? -// TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1683171 +import * as install from "./path/to/generated/files/install.js"; + +assert.strictEqual("2019-03-25-05:00", await install.firstRun.testGetValueAsString()); ``` +
+ +
+ +{{#include ../../../shared/tab_footer.md}} + +### `testHasValue` + +Whether or not **any** value was recorded for a given datetime metric. + +{{#include ../../../shared/tab_header.md}} + +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Install +assertTrue(Install.firstRun.testHasValue()) +``` + +
+ +
+ +```Java +import org.mozilla.yourApplication.GleanMetrics.Install; + +assertTrue(Install.INSTANCE.firstRun.testHasValue()); +``` + +
+ +
+ +```Swift +@testable import Glean + +XCTAssert(Install.firstRun.testHasValue()) +```
+
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +assert metrics.install.first_run.test_has_value() +``` + +
+ +
+ +
+ +
+ {{#include ../../../shared/tab_footer.md}} -## Limits +### `testGetNumRecordedErrors` -* None. +Get number of errors recorded for a given datetime metric. -## Examples +{{#include ../../../shared/tab_header.md}} -* When did the user first run the application? +
+ +```Kotlin +import mozilla.telemetry.glean.testing.ErrorType +import org.mozilla.yourApplication.GleanMetrics.Install + +assertEquals(0, Install.firstRun.testGetNumRecordedErrors(ErrorType.InvalidValue)) +``` + +
+ +
+ +```Java +import mozilla.telemetry.glean.testing.ErrorType +import org.mozilla.yourApplication.GleanMetrics.Install; + +assertEquals(0, Install.INSTANCE.firstRun.testGetNumRecordedErrors(ErrorType.InvalidValue)); +``` + +
+ +
+ +```Swift +@testable import Glean + +XCTAssertEqual(0, Install.firstRun.getNumRecordedErrors(.invalidValue)) +``` +
+ +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +assert 0 == metrics.install.test_get_num_recorded_errors( + ErrorType.INVALID_VALUE +) +``` + +
+ +
+ +```rust +use glean::ErrorType; +use glean_metrics; + +assert_eq!(0, install::first_run.test_get_num_recorded_errors( + ErrorType::InvalidValue +)); +``` + +
+ +
+ +
+ +{{#include ../../../shared/tab_footer.md}} -## Recorded errors +## Metric parameters -* `invalid_value`: Setting the date time to an invalid value. +Example datetime metric definition: + +```yaml +install: + first_run: + type: datetime + time_unit: day + description: > + Records the date when the application was first run + bugs: + - https://bugzilla.mozilla.org/000000 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3 + notification_emails: + - me@mozilla.com + expires: 2020-10-01 +``` + +For a full reference on metrics parameters common to all metric types, +refer to the metrics [YAML format](../yaml/index.md) reference page. + +### Extra metric parameters + +#### `time_unit` + +Datetimes have a required `time_unit` parameter to specify the smallest unit of resolution that the metric will record. The allowed values for `time_unit` are: + +* `nanosecond` +* `microsecond` +* `millisecond` +* `second` +* `minute` +* `hour` +* `day` + +Carefully consider the required resolution for recording your metric, and choose the coarsest resolution possible. + +## Data questions + +* When did the user first run the application? ## Reference diff --git a/docs/user/reference/metrics/event.md b/docs/user/reference/metrics/event.md index 1ea1950e66..615cc28ff4 100644 --- a/docs/user/reference/metrics/event.md +++ b/docs/user/reference/metrics/event.md @@ -1,6 +1,7 @@ # Events -Events allow recording of e.g. individual occurrences of user actions, say every time a view was open and from where. +Events allow recording of e.g. individual occurrences of user actions, +say every time a view was open and from where. Each event contains the following data: @@ -8,32 +9,18 @@ Each event contains the following data: - The name of the event. - A set of key-value pairs, where the keys are predefined in the `extra_keys` metric parameter, and the values are strings. -> **Important:** events are the most expensive metric type to record, transmit, store and analyze, so they should be used sparingly, and only when none of the other metric types are sufficient for answering your question. +{{#include ../../../shared/blockquote-warning.html}} -## Configuration +##### Important -Say you're adding a new event for when a view is shown. First you need to add an entry for the event to the `metrics.yaml` file: +> Events are the most expensive metric type to record, transmit, store and analyze, +> so they should be used sparingly, and only when none of the other metric types are sufficient for answering your question. -```YAML -views: - login_opened: - type: event - description: > - Recorded when the login view is opened. - ... - extra_keys: - source_of_login: - description: The source from which the login view was opened, e.g. "toolbar". -``` +## Recording API -The `extra_keys` parameter enumerates the acceptable keys on the event. This is -an object mapping the key to an object containing metadata about the key. A -maximum of 10 extra keys is allowed. This metadata object has the following -keys: +### `record` -- `description`: **Required.** A description of the key. - -## API +Record a new event, with optional extra values. {{#include ../../../shared/tab_header.md}} @@ -47,21 +34,14 @@ import org.mozilla.yourApplication.GleanMetrics.Views Views.loginOpened.record(mapOf(Views.loginOpenedKeys.sourceOfLogin to "toolbar")) ``` -There are test APIs available too, for example: +
-```Kotlin -import org.mozilla.yourApplication.GleanMetrics.Views +
-// Was any event recorded? -assertTrue(Views.loginOpened.testHasValue()) -// Get a List of the recorded events. -val snapshot = Views.loginOpened.testGetValue() -// Check that two events were recorded. -assertEquals(2, snapshot.size) -val first = snapshot.single() -assertEquals("login_opened", first.name) -// Check that no errors were recorded -assertEquals(0, Views.loginOpened.testGetNumRecordedErrors(ErrorType.InvalidOverflow)) +```Java +import org.mozilla.yourApplication.GleanMetrics.Views; + +Views.INSTANCE.loginOpened.record(); ```
@@ -74,23 +54,6 @@ Note that an `enum` has been generated for handling the `extra_keys`: it has the Views.loginOpened.record(extra: [.sourceOfLogin: "toolbar"]) ``` -There are test APIs available too, for example: - -```Kotlin -@testable import Glean - -// Was any event recorded? -XCTAssert(Views.loginOpened.testHasValue()) -// Get a List of the recorded events. -val snapshot = try! Views.loginOpened.testGetValue() -// Check that two events were recorded. -XCTAssertEqual(2, snapshot.size) -val first = snapshot[0] -XCTAssertEqual("login_opened", first.name) -// Check that no errors were recorded -XCTAssertEqual(0, Views.loginOpened.testGetNumRecordedErrors(.invalidOverflow)) -``` -
@@ -108,157 +71,345 @@ metrics.views.login_opened.record( ) ``` -There are test APIs available too, for example: +
-```Python -# Was any event recorded? -assert metrics.views.login_opened.test_has_value() -# Get a List of the recorded events. -snapshot = metrics.views.login_opened.test_get_value() -# Check that two events were recorded. -assert 2 == len(snapshot) -first = snapshot[0] -assert "login_opened" == first.name -# Check that no errors were recorded -assert 0 == metrics.views.login_opened.test_get_num_recorded_errors( - ErrorType.INVALID_OVERFLOW -) +
+ +Note that an `enum` has been generated for handling the `extra_keys`: it has the same name as the event metric, with `Keys` added. + +```Rust +use metrics::views; + +let mut extra = HashMap::new(); +extra.insert(views::LoginOpenedKeys::SourceOfLogin, "toolbar".into()); +views::login_opened.record(extra); ```
-
+
-Note that an `enum` has been generated for handling the `extra_keys`: it has the same name as the event metric, with `Keys` added. +```js +import * as views from "./path/to/generated/files/views.js"; + +views.loginOpened.record({ sourceOfLogin: "toolbar" }); +``` + +
-```C# -using static Mozilla.YourApplication.GleanMetrics.Views; +
-Views.loginOpened.Record(new Dictionary { - { Views.loginOpenedKeys.sourceOfLogin, "toolbar" } -}); +**C++** + +```c++ +#include "mozilla/glean/GleanMetrics.h" + +using mozilla::glean::views::LoginOpenedKeys; +nsTArray> extra; +nsCString source = "toolbar"_ns; +extra.AppendElement(MakeTuple(LoginOpenedKeys::SourceOfLogin, source)); + +mozilla::glean::views::login_opened.Record(std::move(extra)) ``` -There are test APIs available too, for example: - -```C# -using static Mozilla.YourApplication.GleanMetrics.Views; - -// Was any event recorded? -Assert.True(Views.loginOpened.TestHasValue()); -// Get a List of the recorded events. -var snapshot = Views.loginOpened.TestGetValue(); -// Check that two events were recorded. -Assert.Equal(2, snapshot.Length); -var first = snapshot.First(); -Assert.Equal("login_opened", first.Name); -// Check that no errors were recorded -Assert.Equal(0, Views.loginOpened.TestGetNumRecordedErrors(ErrorType.InvalidOverflow)); +**JavaScript** + +```js +const extra = { sourceOfLogin: "toolbar" }; +Glean.views.loginOpened.record(extra); ```
-
-Note that an `enum` has been generated for handling the `extra_keys`: it has the same name as the event metric, with `Keys` added. +{{#include ../../../shared/tab_footer.md}} -```rust -use metrics::views; +#### Recorded errors -let mut extra = HashMap::new(); -extra.insert(views::LoginOpenedKeys::SourceOfLogin, "toolbar".into()); -views::login_opened.record(extra); +* [`invalid_overflow`](../../user/metrics/error-reporting.md): if any of the values in the `extras` object are greater than 50 bytes in length. (Prior to Glean 31.5.0, this recorded an `invalid_value`). + +## Testing API + +### `testGetValue` + +Get the list of recorded events. + +{{#include ../../../shared/tab_header.md}} + +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Views + +val snapshot = Views.loginOpened.testGetValue() +assertEquals(2, snapshot.size) +val first = snapshot.single() +assertEquals("login_opened", first.name) +``` + +
+ +
+ +```Java +import org.mozilla.yourApplication.GleanMetrics.Views + +assertEquals(Views.INSTANCE.loginOpened.testGetValue().size) +``` + +
+ +
+ +```swift +@testable import Glean + +val snapshot = try! Views.loginOpened.testGetValue() +XCTAssertEqual(2, snapshot.size) +val first = snapshot[0] +XCTAssertEqual("login_opened", first.name) +``` + +
+ +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +snapshot = metrics.views.login_opened.test_get_value() +assert 2 == len(snapshot) +first = snapshot[0] +assert "login_opened" == first.name ``` -There are test APIs available too, for example: +
+ +
-```rust +```Rust use metrics::views; -// Was any event recorded? -assert!(views::login_opened.test_get_value(None).is_some()); -// Get a List of the recorded events. var snapshot = views::login_opened.test_get_value(None).unwrap(); -// Check that two events were recorded. assert_eq!(2, snapshot.len()); let first = &snapshot[0]; assert_eq!("login_opened", first.name); -// Check that no errors were recorded -assert_eq!(0, views::login_opened.test_get_num_recorded_errors(ErrorType::InvalidOverflow, None)); ```
-
+
-> **Note**: C++ APIs are only available in Firefox Desktop. +```js +import * as views from "./path/to/generated/files/views.js"; + +const snapshot = await views.loginOpened.testGetValue(); +assert.strictEqual(2, snapshot.length); +const first = snapshot[0] +assert.strictEqual("login_opened", first.name) +``` + +
+ +
+ +**C++** ```c++ #include "mozilla/glean/GleanMetrics.h" -using mozilla::glean::views::LoginOpenedKeys; -nsTArray> extra; -nsCString source = "toolbar"_ns; -extra.AppendElement(MakeTuple(LoginOpenedKeys::SourceOfLogin, source)); +auto optEvents = mozilla::glean::views::login_opened.TestGetValue(); +auto events = optEvents.extract(); +ASSERT_EQ(2UL, events.Length()); +ASSERT_STREQ("login_opened", events[0].mName.get()); +``` -mozilla::glean::views::login_opened.Record(std::move(extra)) +**JavaScript** + +```js +var events = Glean.views.loginOpened.testGetValue(); +Assert.equal(2, events.length); +Assert.equal("login_opened", events[0].name); ``` -There are test APIs available too: +
-```c++ -#include "mozilla/glean/GleanMetrics.h" +{{#include ../../../shared/tab_footer.md}} + +### `testHasValue` + +Whether or not any event was recorded for a given event metric. + +{{#include ../../../shared/tab_header.md}} + +
-// Does it have a value? -ASSERT_TRUE(mozilla::glean::views::login_opened.TestGetValue().isSome()); -// Does it have the expected value? -// TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1678567 -// Did it run across any errors? -// TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1683171 +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Views + +assertTrue(Views.loginOpened.testHasValue()) ```
-
+
-> **Note**: JS APIs are only available in Firefox Desktop. +```Java +import org.mozilla.yourApplication.GleanMetrics.Views -```js -let extra = { sourceOfLogin: "toolbar" }; -Glean.views.loginOpened.record(extra); +assertTrue(Views.INSTANCE.loginOpened.testHasValue()) +``` + +
+ +
+ +```swift +@testable import Glean + +XCTAssert(Views.loginOpened.testHasValue()) ``` -There are test APIs available too: +
-```js -// Does it have the expected value? -// TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1678567 -// Did it run across any errors? -// TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1683171 +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +assert metrics.views.login_opened.test_has_value() ```
+
+ +
+ +
+ {{#include ../../../shared/tab_footer.md}} -## Limits +### `testGetNumRecordedErrors` -* When 500 events are queued on the client an events ping is immediately sent. +Get the number of errors recorded for a given event metric. -* The `extra_keys` allows for a maximum of 10 keys. +{{#include ../../../shared/tab_header.md}} -* The keys in the `extra_keys` list must be in dotted snake case, with a maximum length of 40 bytes in UTF-8. +
-* The values in the `extras` object have a maximum length of 50 in UTF-8. - -## Examples +```Kotlin +import mozilla.telemetry.glean.testing.ErrorType +import org.mozilla.yourApplication.GleanMetrics.Views + +assertEquals(0, Views.loginOpened.testGetNumRecordedErrors(ErrorType.InvalidOverflow)) +``` + +
+ +
+ +```Kotlin +import mozilla.telemetry.glean.testing.ErrorType +import org.mozilla.yourApplication.GleanMetrics.Views + +assertEquals(0, Views.INSTANCE.loginOpened.testGetNumRecordedErrors(ErrorType.InvalidOverflow)) +``` + +
+ +
+ +```swift +@testable import Glean + +XCTAssertEqual(0, Views.loginOpened.testGetNumRecordedErrors(.invalidOverflow)) +``` +
+ +
+ +```Python +from glean import load_metrics +from glean.testing import ErrorType +metrics = load_metrics("metrics.yaml") + +assert 0 == metrics.views.login_opened.test_get_num_recorded_errors( + ErrorType.INVALID_OVERFLOW +) +``` + +
+ +
-* Every time a new tab is opened. +```Rust +use glean::ErrorType; +use metrics::views; + +assert_eq!( + 0, + views::login_opened.test_get_num_recorded_errors(ErrorType::InvalidOverflow, None) +); +``` + +
+ +
+ +
+ +{{#include ../../../shared/tab_footer.md}} + +## Metric parameters + +Example event metric definition: + +```yaml +views: + login_opened: + type: event + description: | + Recorded when the login view is opened. + bugs: + - https://bugzilla.mozilla.org/000000 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3 + notification_emails: + - me@mozilla.com + expires: 2020-10-01 + extra_keys: + source_of_login: + description: The source from which the login view was opened, e.g. "toolbar". +``` + +For a full reference on metrics parameters common to all metric types, +refer to the metrics [YAML format](../yaml/index.md) reference page. + +### Extra metric parameters + +#### `extra_keys` + +The acceptable keys on the "extra" object sent with events. +A maximum of 10 extra keys is allowed. -## Recorded errors +Each extra key contains additional metadata: -* `invalid_overflow`: if any of the values in the `extras` object are greater than 50 bytes in length. (Prior to Glean 31.5.0, this recorded an `invalid_value`). +- `description`: **Required.** A description of the key. + +## Data questions + +* When and from where was the login view opened? +## Limits + +* When 500 events are queued on the client an events ping is immediately sent. +* The `extra_keys` allows for a maximum of 10 keys. +* The keys in the `extra_keys` list must be in dotted snake case, with a maximum length of 40 bytes, when encoded as UTF-8. +* The values in the `extras` object have a maximum of 50 bytes, when encoded as UTF-8. + ## Reference * [Kotlin API docs](../../../javadoc/glean/mozilla.telemetry.glean.private/-event-metric-type/index.html) diff --git a/docs/user/reference/metrics/labeled_counters.md b/docs/user/reference/metrics/labeled_counters.md index 2c1c1e8366..3b97fbb834 100644 --- a/docs/user/reference/metrics/labeled_counters.md +++ b/docs/user/reference/metrics/labeled_counters.md @@ -1,6 +1,11 @@ # Labeled Counters Labeled counters are used to record different related counts that should sum up to a total. +Each counter always starts from `0`. +Each time you record to a labeled counter, its value is incremented. +Unless incremented by a positive value, a counter will not be reported in pings, +that means: the value `0` is never sent in a ping. + ## Configuration diff --git a/docs/user/reference/yaml/index.md b/docs/user/reference/yaml/index.md index 6b6049499f..63e0caacbe 100644 --- a/docs/user/reference/yaml/index.md +++ b/docs/user/reference/yaml/index.md @@ -9,6 +9,17 @@ - `description`: **Required.** A textual description of the metric for humans. It should describe what the metric does, what it means for analysts, and its edge cases or any other helpful information. The description field may contain [markdown syntax](https://www.markdownguide.org/basic-syntax/). + + > *Note*: The Glean linter uses a line length limit of 80 characters. + > If your description is longer, e.g. because it includes longer links, + > you can disable `yamllint` using the following annotations (and make sure to enable `yamllint` again as well): + > + > ``` + > # yamllint disable + > description: | + > Your extra long description, that's longer than 80 characters by far. + > # yamllint enable + > ``` - `notification_emails`: **Required.** A list of email addresses to notify for important events with the metric or when people with context or ownership for the metric need to be contacted. For example when a metric's expiration is within in 14 days, emails will be sent from `telemetry-alerts@mozilla.com` to the `notification_emails` addresses associated with the metric. diff --git a/docs/user/user/adding-glean-to-your-project.md b/docs/user/user/adding-glean-to-your-project.md index 2fbe19e9d6..d55a23df5d 100644 --- a/docs/user/user/adding-glean-to-your-project.md +++ b/docs/user/user/adding-glean-to-your-project.md @@ -1,5 +1,7 @@ # Adding Glean to your project + + ## Glean integration checklist The Glean integration checklist can help to ensure your Glean SDK-using product is meeting all of the recommended guidelines. @@ -8,16 +10,16 @@ Products (applications or libraries) using the Glean SDK to collect telemetry ** 1. [Integrate the Glean SDK into the build system](#integrating-with-your-project). Since the Glean SDK does some code generation for your metrics at build time, this requires a few more steps than just adding a library. -2. Include the markdown-formatted documentation generated from the `metrics.yaml` and `pings.yaml` files in the project's documentation. - -3. Go through [data review process](https://wiki.mozilla.org/Firefox/Data_Collection) for all newly collected data. +2. Go through [data review process](https://wiki.mozilla.org/Firefox/Data_Collection) for all newly collected data. -4. Ensure that telemetry coming from automated testing or continuous integration is either not sent to the telemetry server or [tagged with the `automation` tag using the `sourceTag` feature](debugging/index.md#available-debugging-features). +3. Ensure that telemetry coming from automated testing or continuous integration is either not sent to the telemetry server or [tagged with the `automation` tag using the `sourceTag` feature](debugging/index.md#available-debugging-features). -5. [File a data engineering bug][dataeng-bug] to enable your product's application id. +4. [File a data engineering bug][dataeng-bug] to enable your product's application id and have your metrics be indexed by the [Glean Dictionary]. Additionally, applications (but not libraries) **must**: +5. [Initialize Glean](../reference/general/index.md#initializing-the-glean-sdk) as early as possible at application startup. + 6. Provide a way for users to turn data collection off (e.g. providing settings to control `Glean.setUploadEnabled()`). The exact method used is application-specific. ## Usage @@ -316,7 +318,9 @@ metrics.your_category.your_metric.set("value") #### Documentation -The documentation for your application or library's metrics and pings are written in `metrics.yaml` and `pings.yaml`. However, you should also provide human-readable markdown files based on this information, and this is a requirement for Mozilla projects using the Glean SDK. For other languages and platforms, this transformation is done automatically as part of the build. However, for Python the integration to automatically generate docs is an additional step. +The documentation for your application or library's metrics and pings are written in `metrics.yaml` and `pings.yaml`. + +For Mozilla projects, this SDK documentation is automatically published on the [Glean Dictionary](https://dictionary.telemetry.mozilla.org). For non-Mozilla products, it is recommended to generate markdown-based documentation of your metrics and pings into the repository. For most languages and platforms, this transformation can be done automatically as part of the build. However, for some language bindings the integration to automatically generate docs is an additional step. The Glean SDK provides a commandline tool for automatically generating markdown documentation from your `metrics.yaml` and `pings.yaml` files. To perform that translation, run `glean_parser`'s `translate` command: @@ -429,3 +433,4 @@ In practice, this should not be a performance issue: since the work is already i In order to make testing metrics easier 'out of the box', all metrics include a set of test API functions in order to facilitate unit testing. These include functions to test whether a value has been stored, and functions to retrieve the stored value for validation. For more information, please refer to [Unit testing Glean metrics](testing-metrics.md). [dataeng-bug]: https://bugzilla.mozilla.org/enter_bug.cgi?assigned_to=nobody%40mozilla.org&bug_ignored=0&bug_severity=--&bug_status=NEW&bug_type=task&cf_fx_iteration=---&cf_fx_points=---&comment=Application%20friendly%20name%3A%20my_app_name%0D%0AApplication%20ID%3A%20org.mozilla.my_app_id%0D%0ADescription%3A%20Brief%20description%20of%20your%20application%0D%0AGit%20Repository%20URL%3A%20https%3A%2F%2Fgithub.com%2Fmozilla%2Fmy_app_name%0D%0ALocations%20of%20%60metrics.yaml%60%20files%20%28can%20be%20many%29%3A%0D%0A%20%20-%20src%2Fmetrics.yaml%0D%0ALocations%20of%20%60pings.yaml%60%20files%20%28can%20be%20many%29%3A%0D%0A%20-%20src%2Fpings.yaml%0D%0ADependencies%2A%3A%0D%0A%20-%20org.mozilla.components%3Aservice-glean%0D%0ARetention%20Days%3A%20N%2A%2A%0D%0A%0D%0A%0D%0A%2A%20Dependencies%20can%20be%20found%20%5Bin%20the%20Glean%20repositories%5D%28https%3A%2F%2Fprobeinfo.telemetry.mozilla.org%2Fglean%2Frepositories%29.%20Each%20dependency%20must%20be%20listed%20explicitly.%20For%20example%2C%20the%20default%20Glean%20probes%20will%20only%20be%20included%20if%20glean%20itself%20is%20a%20dependency.%0D%0A%0D%0A%2A%2A%20Number%20of%20days%20that%20raw%20data%20will%20be%20retained.%20A%20good%20default%20is%20180.%0D%0A%0D%0AIf%20you%20need%20new%20dependencies%2C%20please%20file%20new%20bugs%20for%20them%2C%20separately%20from%20this%20one.%20For%20any%20questions%2C%20ask%20in%20the%20%23glean%20channel.&component=General&contenttypemethod=list&contenttypeselection=text%2Fplain&defined_groups=1&filed_via=standard_form&flag_type-4=X&flag_type-607=X&flag_type-800=X&flag_type-803=X&flag_type-936=X&form_name=enter_bug&maketemplate=Remember%20values%20as%20bookmarkable%20template&needinfo_from=fbertsch%40mozilla.com%2C%20&op_sys=Unspecified&priority=--&product=Data%20Platform%20and%20Tools&rep_platform=Unspecified&short_desc=Enable%20new%20Glean%20App%20my_app_name&target_milestone=---&version=unspecified +[Glean Dictionary]: https://dictionary.telemetry.mozilla.org diff --git a/docs/user/user/collected-metrics/metrics.md b/docs/user/user/collected-metrics/metrics.md index 0a70ad33c8..33245d567a 100644 --- a/docs/user/user/collected-metrics/metrics.md +++ b/docs/user/user/collected-metrics/metrics.md @@ -1,4 +1,4 @@ - + # Metrics @@ -89,7 +89,7 @@ This ping includes the [client id](https://mozilla.github.io/glean/book/user/pin **Data reviews for this ping:** - -- +- **Bugs related to this ping:** @@ -168,5 +168,5 @@ In addition to those built-in metrics, the following metrics are added to the pi Data categories are [defined here](https://wiki.mozilla.org/Firefox/Data_Collection). - + diff --git a/glean-core/Cargo.toml b/glean-core/Cargo.toml index 8aff5a74d0..500481494c 100644 --- a/glean-core/Cargo.toml +++ b/glean-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean-core" -version = "37.0.0" +version = "38.0.0" authors = ["Jan-Erik Rediger ", "The Glean Team "] description = "A modern Telemetry library" repository = "https://github.com/mozilla/glean" diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt index 2f88c5fdd6..63beb449a5 100644 --- a/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt @@ -562,19 +562,8 @@ open class GleanInternalAPI internal constructor () { // Please note that the following metrics must be set synchronously, so // that they are guaranteed to be available with the first ping that is // generated. We use an internal only API to do that. - // https://developer.android.com/reference/android/os/Build.VERSION - GleanInternalMetrics.androidSdkVersion.setSync(Build.VERSION.SDK_INT.toString()) - GleanInternalMetrics.osVersion.setSync(Build.VERSION.RELEASE) - // https://developer.android.com/reference/android/os/Build - GleanInternalMetrics.deviceManufacturer.setSync(Build.MANUFACTURER) - GleanInternalMetrics.deviceModel.setSync(Build.MODEL) - GleanInternalMetrics.architecture.setSync(Build.SUPPORTED_ABIS[0]) - GleanInternalMetrics.locale.setSync(getLocaleTag()) - - configuration.channel?.let { - GleanInternalMetrics.appChannel.setSync(it) - } + // Set required information first. buildInfo?.let { GleanInternalMetrics.appBuild.setSync(it.versionCode) GleanInternalMetrics.appDisplayVersion.setSync(it.versionName) @@ -604,6 +593,21 @@ open class GleanInternalAPI internal constructor () { packageInfo.versionName?.let { it } ?: "Unknown" ) } + + GleanInternalMetrics.architecture.setSync(Build.SUPPORTED_ABIS[0]) + GleanInternalMetrics.osVersion.setSync(Build.VERSION.RELEASE) + + // Optional data is set last. + + configuration.channel?.let { + GleanInternalMetrics.appChannel.setSync(it) + } + // https://developer.android.com/reference/android/os/Build.VERSION + GleanInternalMetrics.androidSdkVersion.setSync(Build.VERSION.SDK_INT.toString()) + // https://developer.android.com/reference/android/os/Build + GleanInternalMetrics.deviceManufacturer.setSync(Build.MANUFACTURER) + GleanInternalMetrics.deviceModel.setSync(Build.MODEL) + GleanInternalMetrics.locale.setSync(getLocaleTag()) } /** diff --git a/glean-core/android/src/test/java/mozilla/telemetry/glean/debug/GleanDebugActivityTest.kt b/glean-core/android/src/test/java/mozilla/telemetry/glean/debug/GleanDebugActivityTest.kt index 8bc4007a91..ea90af0d3a 100644 --- a/glean-core/android/src/test/java/mozilla/telemetry/glean/debug/GleanDebugActivityTest.kt +++ b/glean-core/android/src/test/java/mozilla/telemetry/glean/debug/GleanDebugActivityTest.kt @@ -16,6 +16,7 @@ import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Ignore import android.content.pm.ActivityInfo import android.content.pm.ResolveInfo import androidx.test.core.app.ActivityScenario.launch @@ -60,6 +61,7 @@ private class TestPingTagClient( } } +@Ignore("Fails with robolectric 4.5.1 - see bug 1698471") @RunWith(AndroidJUnit4::class) class GleanDebugActivityTest { diff --git a/glean-core/android/src/test/java/mozilla/telemetry/glean/private/EventMetricTypeTest.kt b/glean-core/android/src/test/java/mozilla/telemetry/glean/private/EventMetricTypeTest.kt index 89b3deb320..95f3efc558 100644 --- a/glean-core/android/src/test/java/mozilla/telemetry/glean/private/EventMetricTypeTest.kt +++ b/glean-core/android/src/test/java/mozilla/telemetry/glean/private/EventMetricTypeTest.kt @@ -495,7 +495,7 @@ class EventMetricTypeTest { category = "telemetry", name = "test_event", lifetime = Lifetime.Ping, - sendInPings = listOf(pingName), + sendInPings = listOf(pingName, "events"), // also send in builtin ping allowedExtraKeys = listOf("someExtra") ) @@ -523,11 +523,14 @@ class EventMetricTypeTest { sendIfEmpty = false, reasonCodes = listOf()) - // Trigger worker task to upload the pings in the background + // Trigger worker task to upload the pings in the background. + // Because this also triggers the builtin "events" ping + // we should definitely get _something_. triggerWorkManager(context) // We can't properly test the absence of data, // but we can try to receive one and that causes an exception if there is none. + // We also get the "events" ping, which we'll simply ignore here. assertNull(waitForPingContent(pingName, null, server)) // Now try to manually submit the ping. diff --git a/glean-core/csharp/Glean/Glean.csproj b/glean-core/csharp/Glean/Glean.csproj index afe3bdb5ed..3107677acb 100644 --- a/glean-core/csharp/Glean/Glean.csproj +++ b/glean-core/csharp/Glean/Glean.csproj @@ -10,7 +10,7 @@ While we're still testing, mark this as a pre-release package. See https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#pre-release-versions --> - 37.0.0 + 38.0.0 Mozilla.Glean Mozilla.Telemetry.Glean https://github.com/mozilla/glean/ diff --git a/glean-core/examples/sample.rs b/glean-core/examples/sample.rs index 919b7d6a61..1d9af4153c 100644 --- a/glean-core/examples/sample.rs +++ b/glean-core/examples/sample.rs @@ -25,6 +25,8 @@ fn main() { upload_enabled: true, max_events: None, delay_ping_lifetime_io: false, + app_build: "unknown".into(), + use_core_mps: false, }; let mut glean = Glean::new(cfg).unwrap(); glean.register_ping_type(&PingType::new("baseline", true, false, vec![])); diff --git a/glean-core/ffi/Cargo.toml b/glean-core/ffi/Cargo.toml index 33fe645b7a..ca9e75c52a 100644 --- a/glean-core/ffi/Cargo.toml +++ b/glean-core/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean-ffi" -version = "37.0.0" +version = "38.0.0" authors = ["Jan-Erik Rediger ", "The Glean Team "] description = "FFI layer for Glean, a modern Telemetry library" repository = "https://github.com/mozilla/glean" @@ -36,7 +36,7 @@ once_cell = "1.2.0" [dependencies.glean-core] path = ".." -version = "37.0.0" +version = "38.0.0" [target.'cfg(target_os = "android")'.dependencies] android_logger = { version = "0.9.0", default-features = false } diff --git a/glean-core/ffi/src/lib.rs b/glean-core/ffi/src/lib.rs index d6f07fe2af..c182e120cf 100644 --- a/glean-core/ffi/src/lib.rs +++ b/glean-core/ffi/src/lib.rs @@ -249,6 +249,8 @@ impl TryFrom<&FfiConfiguration<'_>> for glean_core::Configuration { let upload_enabled = cfg.upload_enabled != 0; let max_events = cfg.max_events.filter(|&&i| i >= 0).map(|m| *m as usize); let delay_ping_lifetime_io = cfg.delay_ping_lifetime_io != 0; + let app_build = "unknown".to_string(); + let use_core_mps = false; Ok(Self { upload_enabled, @@ -257,6 +259,8 @@ impl TryFrom<&FfiConfiguration<'_>> for glean_core::Configuration { language_binding_name, max_events, delay_ping_lifetime_io, + app_build, + use_core_mps, }) } } @@ -301,9 +305,7 @@ pub extern "C" fn glean_set_upload_enabled(flag: u8) { #[no_mangle] pub extern "C" fn glean_submit_ping_by_name(ping_name: FfiStr, reason: FfiStr) -> u8 { with_glean(|glean| { - Ok(glean - .submit_ping_by_name(&ping_name.to_string_fallible()?, reason.as_opt_str()) - .unwrap_or(false)) + Ok(glean.submit_ping_by_name(&ping_name.to_string_fallible()?, reason.as_opt_str())) }) } diff --git a/glean-core/python/setup.py b/glean-core/python/setup.py index 1e79913a6b..41192489bb 100644 --- a/glean-core/python/setup.py +++ b/glean-core/python/setup.py @@ -56,7 +56,7 @@ history = history_file.read() # glean version. Automatically updated by the bin/prepare_release.sh script -version = "37.0.0" +version = "38.0.0" requirements = [ "cffi>=1.13.0", diff --git a/glean-core/rlb/Cargo.toml b/glean-core/rlb/Cargo.toml index f1de90d298..7b7b1abf63 100644 --- a/glean-core/rlb/Cargo.toml +++ b/glean-core/rlb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean" -version = "37.0.0" +version = "38.0.0" authors = ["Jan-Erik Rediger ", "The Glean Team "] description = "Glean SDK Rust language bindings" repository = "https://github.com/mozilla/glean" @@ -22,7 +22,7 @@ maintenance = { status = "actively-developed" } [dependencies.glean-core] path = ".." -version = "37.0.0" +version = "38.0.0" [dependencies] crossbeam-channel = "0.5" diff --git a/glean-core/rlb/examples/prototype.rs b/glean-core/rlb/examples/prototype.rs index a64dbf3d28..f4cc102327 100644 --- a/glean-core/rlb/examples/prototype.rs +++ b/glean-core/rlb/examples/prototype.rs @@ -52,6 +52,7 @@ fn main() { channel: None, server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: true, }; let client_info = ClientInfoMetrics { diff --git a/glean-core/rlb/src/common_test.rs b/glean-core/rlb/src/common_test.rs index c8478fd498..8065432789 100644 --- a/glean-core/rlb/src/common_test.rs +++ b/glean-core/rlb/src/common_test.rs @@ -49,6 +49,7 @@ pub(crate) fn new_glean( channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }, }; diff --git a/glean-core/rlb/src/configuration.rs b/glean-core/rlb/src/configuration.rs index ac5b0392e2..2c541014c8 100644 --- a/glean-core/rlb/src/configuration.rs +++ b/glean-core/rlb/src/configuration.rs @@ -30,4 +30,6 @@ pub struct Configuration { pub server_endpoint: Option, /// The instance of the uploader used to send pings. pub uploader: Option>, + /// Whether Glean should schedule "metrics" pings for you. + pub use_core_mps: bool, } diff --git a/glean-core/rlb/src/dispatcher/global.rs b/glean-core/rlb/src/dispatcher/global.rs index 7db75a6b15..d0aeebb64d 100644 --- a/glean-core/rlb/src/dispatcher/global.rs +++ b/glean-core/rlb/src/dispatcher/global.rs @@ -100,7 +100,8 @@ pub fn shutdown() -> Result<(), DispatchError> { /// Resets the Glean state and triggers init again. pub(crate) fn reset_dispatcher() { // We don't care about shutdown errors, since they will - // definitely happen if this + // definitely happen if this is run concurrently. + // We will still replace the global dispatcher. let _ = shutdown(); // Now that the dispatcher is shut down, replace it. diff --git a/glean-core/rlb/src/dispatcher/mod.rs b/glean-core/rlb/src/dispatcher/mod.rs index d400784dc4..0c1c58aade 100644 --- a/glean-core/rlb/src/dispatcher/mod.rs +++ b/glean-core/rlb/src/dispatcher/mod.rs @@ -385,13 +385,13 @@ mod test { assert!(thread::current().id() != main_thread_id); // Use the canary bool to make sure this is getting called before // the test completes. - assert_eq!(false, canary_clone.load(Ordering::SeqCst)); + assert!(!canary_clone.load(Ordering::SeqCst)); canary_clone.store(true, Ordering::SeqCst); }) .expect("Failed to dispatch the test task"); dispatcher.block_on_queue(); - assert_eq!(true, thread_canary.load(Ordering::SeqCst)); + assert!(thread_canary.load(Ordering::SeqCst)); assert_eq!(main_thread_id, thread::current().id()); } diff --git a/glean-core/rlb/src/lib.rs b/glean-core/rlb/src/lib.rs index 438acd6853..709ebcb44f 100644 --- a/glean-core/rlb/src/lib.rs +++ b/glean-core/rlb/src/lib.rs @@ -28,6 +28,7 @@ //! channel: None, //! server_endpoint: None, //! uploader: None, +//! use_core_mps: false, //! }; //! glean::initialize(cfg, ClientInfoMetrics::unknown()); //! @@ -172,12 +173,19 @@ fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) { /// * `client_info` - the [`ClientInfoMetrics`] values used to set Glean /// core metrics. pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) { + initialize_internal(cfg, client_info); +} + +fn initialize_internal( + cfg: Configuration, + client_info: ClientInfoMetrics, +) -> Option> { if was_initialize_called() { log::error!("Glean should not be initialized multiple times"); - return; + return None; } - std::thread::Builder::new() + let init_handle = std::thread::Builder::new() .name("glean.init".into()) .spawn(move || { let core_cfg = glean_core::Configuration { @@ -187,6 +195,8 @@ pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) { language_binding_name: LANGUAGE_BINDING_NAME.into(), max_events: cfg.max_events, delay_ping_lifetime_io: cfg.delay_ping_lifetime_io, + app_build: client_info.app_build.clone(), + use_core_mps: cfg.use_core_mps, }; let glean = match Glean::new(core_cfg) { @@ -302,7 +312,7 @@ pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) { // Set up information and scheduling for Glean owned pings. Ideally, the "metrics" // ping startup check should be performed before any other ping, since it relies // on being dispatched to the API context before any other metric. - // TODO: start the metrics ping scheduler, will happen in bug 1672951. + glean.start_metrics_ping_scheduler(); // Check if the "dirty flag" is set. That means the product was probably // force-closed. If that's the case, submit a 'baseline' ping with the @@ -313,10 +323,7 @@ pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) { // write lock on the `glean` object. // Note that unwrapping below is safe: the function will return an // `Ok` value for a known ping. - if glean - .submit_ping_by_name("baseline", Some("dirty_startup")) - .unwrap() - { + if glean.submit_ping_by_name("baseline", Some("dirty_startup")) { state.upload_manager.trigger_upload(); } } @@ -347,6 +354,7 @@ pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) { // Mark the initialization as called: this needs to happen outside of the // dispatched block! INITIALIZE_CALLED.store(true, Ordering::SeqCst); + Some(init_handle) } /// Shuts down Glean. @@ -364,6 +372,7 @@ pub fn shutdown() { } crate::launch_with_glean_mut(|glean| { + glean.cancel_metrics_ping_scheduler(); glean.set_dirty_flag(false); }); @@ -464,16 +473,15 @@ pub fn set_upload_enabled(enabled: bool) { let old_enabled = glean.is_upload_enabled(); glean.set_upload_enabled(enabled); - // TODO: Cancel upload and any outstanding metrics ping scheduler - // task. Will happen on bug 1672951. - if !old_enabled && enabled { + glean.start_metrics_ping_scheduler(); // If uploading is being re-enabled, we have to restore the // application-lifetime metrics. initialize_core_metrics(&glean, &state.client_info, state.channel.clone()); } if old_enabled && !enabled { + glean.cancel_metrics_ping_scheduler(); // If uploading is disabled, we need to send the deletion-request ping: // note that glean-core takes care of generating it. state.upload_manager.trigger_upload(); @@ -553,13 +561,13 @@ pub(crate) fn submit_ping_by_name_sync(ping: &str, reason: Option<&str>) { // This won't actually return from `submit_ping_by_name`, but // returning `false` here skips spinning up the uploader below, // which is basically the same. - return Some(false); + return false; } - glean.submit_ping_by_name(&ping, reason.as_deref()).ok() + glean.submit_ping_by_name(&ping, reason.as_deref()) }); - if let Some(true) = submitted_ping { + if submitted_ping { let state = global_state().lock().unwrap(); state.upload_manager.trigger_upload(); } @@ -660,6 +668,19 @@ pub(crate) fn test_get_experiment_data(experiment_id: String) -> RecordedExperim pub(crate) fn destroy_glean(clear_stores: bool) { // Destroy the existing glean instance from glean-core. if was_initialize_called() { + // Reset the dispatcher first (it might still run tasks against the database) + dispatcher::reset_dispatcher(); + + // Wait for any background uploader thread to finish. + // This needs to be done before the check below, + // as the uploader will also try to acquire a lock on the global Glean. + // + // Note: requires the block here, so we drop the lock again. + { + let state = global_state().lock().unwrap(); + state.upload_manager.test_wait_for_upload(); + } + // We need to check if the Glean object (from glean-core) is // initialized, otherwise this will crash on the first test // due to bug 1675215 (this check can be removed once that @@ -674,8 +695,12 @@ pub(crate) fn destroy_glean(clear_stores: bool) { } // Allow us to go through initialization again. INITIALIZE_CALLED.store(false, Ordering::SeqCst); - // Reset the dispatcher. - dispatcher::reset_dispatcher(); + + // If Glean initialization previously didn't finish, + // then the global state might not have been reset + // and thus needs to be cleared here. + let state = global_state().lock().unwrap(); + state.upload_manager.test_clear_upload_thread(); } } @@ -684,9 +709,9 @@ pub(crate) fn destroy_glean(clear_stores: bool) { pub fn test_reset_glean(cfg: Configuration, client_info: ClientInfoMetrics, clear_stores: bool) { destroy_glean(clear_stores); - // Always log pings for tests - //Glean.setLogPings(true) - initialize(cfg, client_info); + if let Some(handle) = initialize_internal(cfg, client_info) { + handle.join().unwrap(); + } } /// Sets a debug view tag. @@ -743,22 +768,16 @@ pub fn set_log_pings(value: bool) { /// /// * `tags` - A vector of at most 5 valid HTTP header values. Individual /// tags must match the regex: "[a-zA-Z0-9-]{1,20}". -/// -/// # Returns -/// -/// This will return `false` in case `value` contains invalid tags and `true` -/// otherwise or if the tag is set before Glean is initialized. -pub fn set_source_tags(tags: Vec) -> bool { +pub fn set_source_tags(tags: Vec) { if was_initialize_called() { - with_glean_mut(|glean| glean.set_source_tags(tags)) + crate::launch_with_glean_mut(|glean| { + glean.set_source_tags(tags); + }); } else { // Glean has not been initialized yet. Cache the provided source tags. let m = PRE_INIT_SOURCE_TAGS.get_or_init(Default::default); let mut lock = m.lock().unwrap(); *lock = tags; - // When setting the source tags before initialization, - // we don't validate the tags, thus this function always returns true. - true } } diff --git a/glean-core/rlb/src/net/mod.rs b/glean-core/rlb/src/net/mod.rs index 3ec71baf00..42845181d5 100644 --- a/glean-core/rlb/src/net/mod.rs +++ b/glean-core/rlb/src/net/mod.rs @@ -69,10 +69,38 @@ impl UploadManager { } } + /// Wait for the last upload thread to end and ensure no new one is started. + pub(crate) fn test_wait_for_upload(&self) { + // Spin-waiting is fine, this is for tests only. + while self + .inner + .thread_running + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_err() + { + thread::yield_now(); + } + } + + /// Clear the flag of a running thread. + /// + /// This should only be called after `test_wait_for_upload` returned + /// and the global Glean object is fully destroyed. + pub(crate) fn test_clear_upload_thread(&self) { + self.inner.thread_running.store(false, Ordering::SeqCst); + } + /// Signals Glean to upload pings at the next best opportunity. pub(crate) fn trigger_upload(&self) { - if self.inner.thread_running.load(Ordering::SeqCst) { - log::debug!("The upload task is already running."); + // If no other upload proces is running, we're the one starting it. + // Need atomic compare/exchange to avoid any further races + // or we can end up with 2+ uploader threads. + if self + .inner + .thread_running + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_err() + { return; } @@ -81,12 +109,10 @@ impl UploadManager { thread::Builder::new() .name("glean.upload".into()) .spawn(move || { - // Mark the uploader as running. - inner.thread_running.store(true, Ordering::SeqCst); - loop { let incoming_task = with_glean(|glean| glean.get_upload_task()); + log::trace!("Received upload task: {:?}", incoming_task); match incoming_task { PingUploadTask::Upload(request) => { let doc_id = request.document_id.clone(); diff --git a/glean-core/rlb/src/private/labeled.rs b/glean-core/rlb/src/private/labeled.rs index b24631edaa..3fbae149ce 100644 --- a/glean-core/rlb/src/private/labeled.rs +++ b/glean-core/rlb/src/private/labeled.rs @@ -346,7 +346,7 @@ mod test { let invalid_label = "!#I'm invalid#--_"; metric.get(invalid_label).set(true); - assert_eq!(true, metric.get("__other__").test_get_value(None).unwrap()); + assert!(metric.get("__other__").test_get_value(None).unwrap()); assert_eq!( 1, metric.test_get_num_recorded_errors(ErrorType::InvalidLabel, None) diff --git a/glean-core/rlb/src/test.rs b/glean-core/rlb/src/test.rs index acfccdbe75..807d058eb4 100644 --- a/glean-core/rlb/src/test.rs +++ b/glean-core/rlb/src/test.rs @@ -45,6 +45,7 @@ fn send_a_ping() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }; let _t = new_glean(Some(cfg), true); @@ -57,7 +58,7 @@ fn send_a_ping() { // Wait for the ping to arrive. let url = r.recv().unwrap(); - assert_eq!(url.contains(PING_NAME), true); + assert!(url.contains(PING_NAME)); } #[test] @@ -155,6 +156,7 @@ fn test_experiments_recording_before_glean_inits() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }, ClientInfoMetrics::unknown(), false, @@ -215,6 +217,7 @@ fn sending_of_foreground_background_pings() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }; let _t = new_glean(Some(cfg), true); @@ -299,6 +302,7 @@ fn sending_of_startup_baseline_ping() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }, ClientInfoMetrics::unknown(), false, @@ -306,7 +310,7 @@ fn sending_of_startup_baseline_ping() { // Wait for the ping to arrive. let url = r.recv().unwrap(); - assert_eq!(url.contains("baseline"), true); + assert!(url.contains("baseline")); } #[test] @@ -359,6 +363,7 @@ fn no_dirty_baseline_on_clean_shutdowns() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }, ClientInfoMetrics::unknown(), false, @@ -390,17 +395,13 @@ fn initialize_must_not_crash_if_data_dir_is_messed_up() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }; test_reset_glean(cfg, ClientInfoMetrics::unknown(), false); - // TODO(bug 1675215): ensure initialize runs through dispatcher. - // Glean init is async and, for this test, it bails out early due to - // an caused by not being able to create the data dir: we can do nothing - // but wait. Tests in other bindings use the dispatcher's test mode, which - // runs tasks sequentially on the main thread, so no sleep is required, - // because we're guaranteed that, once we reach this point, the full - // init potentially ran. - std::thread::sleep(std::time::Duration::from_secs(3)); + + // We don't need to sleep here. + // The `test_reset_glean` already waited on the initialize task. } #[test] @@ -453,14 +454,17 @@ fn initializing_twice_is_a_noop() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }, ClientInfoMetrics::unknown(), true, ); - crate::block_on_dispatcher(); + // Glean was initialized and it waited for a full initialization to finish. + // We now just want to try to initialize again. + // This will bail out early. - test_reset_glean( + crate::initialize( Configuration { data_path: tmpname, application_id: GLOBAL_APPLICATION_ID.into(), @@ -470,17 +474,16 @@ fn initializing_twice_is_a_noop() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }, ClientInfoMetrics::unknown(), - false, ); - // TODO(bug 1675215): ensure initialize runs through dispatcher. - // Glean init is async and, for this test, it bails out early due to - // being initialized: we can do nothing but wait. Tests in other bindings use - // the dispatcher's test mode, which runs tasks sequentially on the main - // thread, so no sleep is required. Bug 1675215 might fix this, as well. - std::thread::sleep(std::time::Duration::from_secs(3)); + // We don't need to sleep here. + // The `test_reset_glean` already waited on the initialize task, + // and the 2nd initialize will bail out early. + // + // All we tested is that this didn't crash. } #[test] @@ -508,6 +511,7 @@ fn the_app_channel_must_be_correctly_set_if_requested() { channel: None, server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }, ClientInfoMetrics::unknown(), true, @@ -622,6 +626,7 @@ fn sending_deletion_ping_if_disabled_outside_of_run() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }; let _t = new_glean(Some(cfg), true); @@ -640,6 +645,7 @@ fn sending_deletion_ping_if_disabled_outside_of_run() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }, ClientInfoMetrics::unknown(), false, @@ -647,7 +653,7 @@ fn sending_deletion_ping_if_disabled_outside_of_run() { // Wait for the ping to arrive. let url = r.recv().unwrap(); - assert_eq!(url.contains("deletion-request"), true); + assert!(url.contains("deletion-request")); } #[test] @@ -687,6 +693,7 @@ fn no_sending_of_deletion_ping_if_unchanged_outside_of_run() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }; let _t = new_glean(Some(cfg), true); @@ -705,6 +712,7 @@ fn no_sending_of_deletion_ping_if_unchanged_outside_of_run() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }, ClientInfoMetrics::unknown(), false, @@ -770,6 +778,7 @@ fn setting_debug_view_tag_before_initialization_should_not_crash() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }; let _t = new_glean(Some(cfg), true); @@ -829,9 +838,77 @@ fn setting_source_tags_before_initialization_should_not_crash() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, + }; + + let _t = new_glean(Some(cfg), true); + crate::block_on_dispatcher(); + + // Submit a baseline ping. + submit_ping_by_name("baseline", Some("background")); + + // Wait for the ping to arrive. + let headers = r.recv().unwrap(); + assert_eq!( + "valid-tag1,valid-tag2", + headers + .iter() + .find(|&kv| kv.0 == "X-Source-Tags") + .unwrap() + .1 + ); +} + +#[test] +fn setting_source_tags_after_initialization_should_not_crash() { + let _lock = lock_test(); + + destroy_glean(true); + assert!(!was_initialize_called()); + + // Define a fake uploader that reports back the submission headers + // using a crossbeam channel. + let (s, r) = crossbeam_channel::bounded::>(1); + + #[derive(Debug)] + pub struct FakeUploader { + sender: crossbeam_channel::Sender>, + } + impl net::PingUploader for FakeUploader { + fn upload( + &self, + _url: String, + _body: Vec, + headers: Vec<(String, String)>, + ) -> net::UploadResult { + self.sender.send(headers).unwrap(); + net::UploadResult::HttpStatus(200) + } + } + + // Create a custom configuration to use a fake uploader. + let dir = tempfile::tempdir().unwrap(); + let tmpname = dir.path().to_path_buf(); + + let cfg = Configuration { + data_path: tmpname, + application_id: GLOBAL_APPLICATION_ID.into(), + upload_enabled: true, + max_events: None, + delay_ping_lifetime_io: false, + channel: Some("testing".into()), + server_endpoint: Some("invalid-test-host".into()), + uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }; let _t = new_glean(Some(cfg), true); + + // Attempt to set source tags after `Glean.initialize` is called, + // but before Glean is fully initialized. + assert!(was_initialize_called()); + set_source_tags(vec!["valid-tag1".to_string(), "valid-tag2".to_string()]); + crate::block_on_dispatcher(); // Submit a baseline ping. @@ -895,6 +972,7 @@ fn flipping_upload_enabled_respects_order_of_events() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }; // We create a ping and a metric before we initialize Glean @@ -919,7 +997,7 @@ fn flipping_upload_enabled_respects_order_of_events() { // Wait for the ping to arrive. let url = r.recv().unwrap(); - assert_eq!(url.contains("deletion-request"), true); + assert!(url.contains("deletion-request")); } #[test] @@ -965,6 +1043,7 @@ fn registering_pings_before_init_must_work() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }; let _t = new_glean(Some(cfg), true); @@ -1015,6 +1094,7 @@ fn test_a_ping_before_submission() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(FakeUploader { sender: s })), + use_core_mps: false, }; let _t = new_glean(Some(cfg), true); diff --git a/glean-core/rlb/tests/init_fails.rs b/glean-core/rlb/tests/init_fails.rs index 278fdb31ea..f8a3774a0a 100644 --- a/glean-core/rlb/tests/init_fails.rs +++ b/glean-core/rlb/tests/init_fails.rs @@ -68,6 +68,7 @@ fn init_fails() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }; common::initialize(cfg); diff --git a/glean-core/rlb/tests/no_time_to_init.rs b/glean-core/rlb/tests/no_time_to_init.rs index 6fbe9f7ef3..ee37cb4281 100644 --- a/glean-core/rlb/tests/no_time_to_init.rs +++ b/glean-core/rlb/tests/no_time_to_init.rs @@ -66,6 +66,7 @@ fn init_fails() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }; common::initialize(cfg); diff --git a/glean-core/rlb/tests/overflowing_preinit.rs b/glean-core/rlb/tests/overflowing_preinit.rs index 0ca3d5d4be..94381d006c 100644 --- a/glean-core/rlb/tests/overflowing_preinit.rs +++ b/glean-core/rlb/tests/overflowing_preinit.rs @@ -71,6 +71,7 @@ fn overflowing_the_task_queue_records_telemetry() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }; // Insert a bunch of tasks to overflow the queue. diff --git a/glean-core/rlb/tests/schema.rs b/glean-core/rlb/tests/schema.rs index a2cf617644..1f2b119767 100644 --- a/glean-core/rlb/tests/schema.rs +++ b/glean-core/rlb/tests/schema.rs @@ -36,6 +36,7 @@ fn new_glean(configuration: Option) -> tempfile::TempDir { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }, }; @@ -86,6 +87,7 @@ fn validate_against_schema() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: Some(Box::new(ValidatingUploader { sender: s })), + use_core_mps: false, }; let _ = new_glean(Some(cfg)); diff --git a/glean-core/rlb/tests/simple.rs b/glean-core/rlb/tests/simple.rs index 8d8a009dcd..923da54139 100644 --- a/glean-core/rlb/tests/simple.rs +++ b/glean-core/rlb/tests/simple.rs @@ -68,6 +68,7 @@ fn simple_lifecycle() { channel: Some("testing".into()), server_endpoint: Some("invalid-test-host".into()), uploader: None, + use_core_mps: false, }; common::initialize(cfg); diff --git a/glean-core/src/event_database/mod.rs b/glean-core/src/event_database/mod.rs index 9b14d748cc..2648a8134f 100644 --- a/glean-core/src/event_database/mod.rs +++ b/glean-core/src/event_database/mod.rs @@ -8,7 +8,6 @@ use std::fs::{create_dir_all, File, OpenOptions}; use std::io::BufRead; use std::io::BufReader; use std::io::Write; -use std::iter::FromIterator; use std::path::{Path, PathBuf}; use std::sync::RwLock; @@ -161,15 +160,7 @@ impl EventDatabase { let mut ping_sent = false; for store_name in store_names { - if let Err(err) = glean.submit_ping_by_name(&store_name, Some("startup")) { - log::warn!( - "Error flushing existing events to the '{}' ping: {}", - store_name, - err - ); - } else { - ping_sent = true; - } + ping_sent |= glean.submit_ping_by_name(&store_name, Some("startup")); } ping_sent @@ -225,14 +216,7 @@ impl EventDatabase { // If any of the event stores reached maximum size, submit the pings // containing those events immediately. for store_name in stores_to_submit { - if let Err(err) = glean.submit_ping_by_name(store_name, Some("max_capacity")) { - log::warn!( - "Got more than {} events, but could not persist {} ping: {}", - glean.get_max_events(), - store_name, - err - ); - } + glean.submit_ping_by_name(store_name, Some("max_capacity")); } } @@ -277,9 +261,11 @@ impl EventDatabase { // in a single location. store.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); let first_timestamp = store[0].timestamp; - Some(JsonValue::from_iter( - store.iter().map(|e| e.serialize_relative(first_timestamp)), - )) + let snapshot = store + .iter() + .map(|e| e.serialize_relative(first_timestamp)) + .collect(); + Some(snapshot) } else { log::warn!("Unexpectly got empty event store for '{}'", store_name); None diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index fb40c1de63..e4ae9ad35f 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -38,6 +38,7 @@ mod internal_metrics; mod internal_pings; pub mod metrics; pub mod ping; +mod scheduler; pub mod storage; mod system; pub mod traits; @@ -129,6 +130,11 @@ pub struct Configuration { pub max_events: Option, /// Whether Glean should delay persistence of data from metrics with ping lifetime. pub delay_ping_lifetime_io: bool, + /// The application's build identifier. If this is different from the one provided for a previous init, + /// and use_core_mps is `true`, we will trigger a "metrics" ping. + pub app_build: String, + /// Whether Glean should schedule "metrics" pings. + pub use_core_mps: bool, } /// The object holding meta information about a Glean instance. @@ -147,6 +153,8 @@ pub struct Configuration { /// upload_enabled: true, /// max_events: None, /// delay_ping_lifetime_io: false, +/// app_build: "".into(), +/// use_core_mps: false, /// }; /// let mut glean = Glean::new(cfg).unwrap(); /// let ping = PingType::new("sample", true, false, vec![]); @@ -161,7 +169,7 @@ pub struct Configuration { /// /// call_counter.add(&glean, 1); /// -/// glean.submit_ping(&ping, None).unwrap(); +/// glean.submit_ping(&ping, None); /// ``` /// /// ## Note @@ -185,6 +193,8 @@ pub struct Glean { is_first_run: bool, upload_manager: PingUploadManager, debug: DebugOptions, + app_build: String, + schedule_metrics_pings: bool, } impl Glean { @@ -208,7 +218,7 @@ impl Glean { /* seconds per interval */ 60, /* max pings per interval */ 15, ); - // We only scan the pending ping sdirectories when calling this from a subprocess, + // We only scan the pending ping directories when calling this from a subprocess, // when calling this from ::new we need to scan the directories after dealing with the upload state. if scan_directories { let _scanning_thread = upload_manager.scan_pending_pings_directories(); @@ -233,6 +243,9 @@ impl Glean { max_events: cfg.max_events.unwrap_or(DEFAULT_MAX_EVENTS), is_first_run: false, debug: DebugOptions::new(), + app_build: cfg.app_build.to_string(), + // Subprocess doesn't use "metrics" pings so has no need for a scheduler. + schedule_metrics_pings: false, }; // Can't use `local_now_with_offset_and_record` above, because we needed a valid `Glean` first. @@ -288,6 +301,9 @@ impl Glean { } } + // We set this only for non-subprocess situations. + glean.schedule_metrics_pings = cfg.use_core_mps; + // We only scan the pendings pings directories **after** dealing with the upload state. // If upload is disabled, we delete all pending pings files // and we need to do that **before** scanning the pending pings folder @@ -311,6 +327,8 @@ impl Glean { upload_enabled, max_events: None, delay_ping_lifetime_io: false, + app_build: "unknown".into(), + use_core_mps: false, }; let mut glean = Self::new(cfg).unwrap(); @@ -454,8 +472,8 @@ impl Glean { } else { Some("set_upload_enabled") }; - if let Err(err) = self.internal_pings.deletion_request.submit(self, reason) { - log::error!("Failed to submit deletion-request ping on optout: {}", err); + if !self.internal_pings.deletion_request.submit(self, reason) { + log::error!("Failed to submit deletion-request ping on optout."); } self.clear_metrics(); self.upload_enabled = false; @@ -626,10 +644,10 @@ impl Glean { /// # Returns /// /// Whether the ping was succesfully assembled and queued. - pub fn submit_ping(&self, ping: &PingType, reason: Option<&str>) -> Result { + pub fn submit_ping(&self, ping: &PingType, reason: Option<&str>) -> bool { if !self.is_upload_enabled() { log::info!("Glean disabled: not submitting any pings."); - return Ok(false); + return false; } let ping_maker = PingMaker::new(); @@ -641,7 +659,7 @@ impl Glean { "No content for ping '{}', therefore no ping queued.", ping.name ); - Ok(false) + false } Some(ping) => { // This metric is recorded *after* the ping is collected (since @@ -656,7 +674,12 @@ impl Glean { if let Err(e) = ping_maker.store_ping(&self.get_data_path(), &ping) { log::warn!("IO error while writing ping to file: {}. Enqueuing upload of what we have in memory.", e); self.additional_metrics.io_errors.add(self, 1); - let content = ::serde_json::to_string(&ping.content)?; + // `serde_json::to_string` only fails if serialization of the content + // fails or it contains maps with non-string keys. + // However `ping.content` is already a `JsonValue`, + // so both scenarios should be impossible. + let content = + ::serde_json::to_string(&ping.content).expect("ping serialization failed"); self.upload_manager.enqueue_ping( self, ping.doc_id, @@ -664,8 +687,7 @@ impl Glean { &content, Some(ping.headers), ); - // Not actually 100% 'Ok'. bug 1704606 - return Ok(true); + return true; } self.upload_manager.enqueue_ping_from_file(self, &doc_id); @@ -674,7 +696,8 @@ impl Glean { "The ping '{}' was submitted and will be sent as soon as possible", ping.name ); - Ok(true) + + true } } } @@ -699,11 +722,11 @@ impl Glean { /// # Errors /// /// If collecting or writing the ping to disk failed. - pub fn submit_ping_by_name(&self, ping_name: &str, reason: Option<&str>) -> Result { + pub fn submit_ping_by_name(&self, ping_name: &str, reason: Option<&str>) -> bool { match self.get_ping_by_name(ping_name) { None => { log::error!("Attempted to submit unknown ping '{}'", ping_name); - Ok(false) + false } Some(ping) => self.submit_ping(ping, reason), } @@ -917,8 +940,8 @@ impl Glean { /// This functions generates a baseline ping with reason `active` /// and then sets the dirty bit. pub fn handle_client_active(&mut self) { - if let Err(err) = self.internal_pings.baseline.submit(self, Some("active")) { - log::warn!("Failed to submit baseline ping on active: {}", err); + if !self.internal_pings.baseline.submit(self, Some("active")) { + log::info!("baseline ping not submitted on active"); } self.set_dirty_flag(true); @@ -929,12 +952,12 @@ impl Glean { /// This functions generates a baseline and an events ping with reason /// `inactive` and then clears the dirty bit. pub fn handle_client_inactive(&mut self) { - if let Err(err) = self.internal_pings.baseline.submit(self, Some("inactive")) { - log::warn!("Failed to submit baseline ping on inactive: {}", err); + if !self.internal_pings.baseline.submit(self, Some("inactive")) { + log::info!("baseline ping not submitted on inactive"); } - if let Err(err) = self.internal_pings.events.submit(self, Some("inactive")) { - log::warn!("Failed to submit events ping on inactive: {}", err); + if !self.internal_pings.events.submit(self, Some("inactive")) { + log::info!("events ping not submitted on inactive"); } self.set_dirty_flag(false); @@ -989,6 +1012,22 @@ impl Glean { // We don't care about this failing, maybe the data does just not exist. let _ = self.event_data_store.clear_all(); } + + /// Instructs the Metrics Ping Scheduler's thread to exit cleanly. + /// If Glean was configured with `use_core_mps: false`, this has no effect. + pub fn cancel_metrics_ping_scheduler(&self) { + if self.schedule_metrics_pings { + scheduler::cancel(); + } + } + + /// Instructs the Metrics Ping Scheduler to being scheduling metrics pings. + /// If Glean wsa configured with `use_core_mps: false`, this has no effect. + pub fn start_metrics_ping_scheduler(&self) { + if self.schedule_metrics_pings { + scheduler::schedule(&self); + } + } } /// Returns a timestamp corresponding to "now" with millisecond precision. diff --git a/glean-core/src/lib_unit_tests.rs b/glean-core/src/lib_unit_tests.rs index 52e4a8bf9d..1a12ea2d4d 100644 --- a/glean-core/src/lib_unit_tests.rs +++ b/glean-core/src/lib_unit_tests.rs @@ -813,11 +813,11 @@ fn test_setting_debug_view_tag() { let (mut glean, _) = new_glean(Some(dir)); let valid_tag = "valid-tag"; - assert_eq!(true, glean.set_debug_view_tag(valid_tag)); + assert!(glean.set_debug_view_tag(valid_tag)); assert_eq!(valid_tag, glean.debug_view_tag().unwrap()); let invalid_tag = "invalid tag"; - assert_eq!(false, glean.set_debug_view_tag(invalid_tag)); + assert!(!glean.set_debug_view_tag(invalid_tag)); assert_eq!(valid_tag, glean.debug_view_tag().unwrap()); } @@ -893,9 +893,8 @@ fn records_io_errors() { // Writing the ping file should fail. let submitted = glean.internal_pings.metrics.submit(&glean, None); - // But the return value is still Ok(true) because we enqueue the ping anyway. - assert!(submitted.is_ok()); - assert!(submitted.unwrap()); + // But the return value is still `true` because we enqueue the ping anyway. + assert!(submitted); let metric = &glean.additional_metrics.io_errors; assert_eq!( @@ -909,7 +908,7 @@ fn records_io_errors() { // Now we can submit a ping let submitted = glean.internal_pings.metrics.submit(&glean, None); - assert!(submitted.is_ok()); + assert!(submitted); } #[test] diff --git a/glean-core/src/metrics/ping.rs b/glean-core/src/metrics/ping.rs index fd44b06dec..2aacbcf3a1 100644 --- a/glean-core/src/metrics/ping.rs +++ b/glean-core/src/metrics/ping.rs @@ -2,7 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::error::Result; use crate::Glean; /// Stores information about a ping. @@ -60,7 +59,7 @@ impl PingType { /// # Returns /// /// See [`Glean::submit_ping`](crate::Glean::submit_ping) for details. - pub fn submit(&self, glean: &Glean, reason: Option<&str>) -> Result { + pub fn submit(&self, glean: &Glean, reason: Option<&str>) -> bool { let corrected_reason = match reason { Some(reason) => { if self.reason_codes.contains(&reason.to_string()) { diff --git a/glean-core/src/metrics/string.rs b/glean-core/src/metrics/string.rs index cb8dbc8b12..51be483694 100644 --- a/glean-core/src/metrics/string.rs +++ b/glean-core/src/metrics/string.rs @@ -61,6 +61,12 @@ impl StringMetric { glean.storage().record(glean, &self.meta, &value) } + /// Non-exported API used for crate-internal storage. + /// Gets the current-stored value as a string, or None if there is no value. + pub(crate) fn get_value(&self, glean: &Glean, storage_name: &str) -> Option { + self.test_get_value(&glean, &storage_name) + } + /// **Test-only API (exported for FFI purposes).** /// /// Gets the currently stored value as a string. diff --git a/glean-core/src/scheduler.rs b/glean-core/src/scheduler.rs new file mode 100644 index 0000000000..a618112021 --- /dev/null +++ b/glean-core/src/scheduler.rs @@ -0,0 +1,515 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! # Metrics Ping Scheduler +//! +//! The Metrics Ping Scheduler (MPS) is responsible for scheduling "metrics" pings. +//! It implements the spec described in +//! [the docs](https://mozilla.github.io/glean/book/user/pings/metrics.html#scheduling) + +use crate::metrics::{DatetimeMetric, StringMetric, TimeUnit}; +use crate::{local_now_with_offset, CommonMetricData, Glean, Lifetime, INTERNAL_STORAGE}; +use chrono::prelude::*; +use chrono::Duration; +use once_cell::sync::Lazy; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::JoinHandle; + +const SCHEDULED_HOUR: u32 = 4; + +// Clippy thinks an AtomicBool would be preferred, but Condvar requires a full Mutex. +// See https://github.com/rust-lang/rust-clippy/issues/1516 +#[allow(clippy::mutex_atomic)] +static TASK_CONDVAR: Lazy, Condvar)>> = + Lazy::new(|| Arc::new((Mutex::new(false), Condvar::new()))); + +/// Describes the interface for a submitter of "metrics" pings. +/// Used to decouple the implementation so we can test it. +trait MetricsPingSubmitter { + /// Submits a metrics ping, updating the last sent time to `now` + /// (which might not be _right now_ due to processing delays (or in tests)) + fn submit_metrics_ping(&self, glean: &Glean, reason: Option<&str>, now: DateTime); +} + +/// Describes the interface for a scheduler of "metrics" pings. +/// Used to decouple the implementation so we can test it. +trait MetricsPingScheduler { + /// Begins a recurring schedule of "metrics" ping submissions, on another thread. + /// `now` is used with `when` to determine the first schedule interval and + /// may not be _right now_ due to processing delays (or in tests). + fn start_scheduler( + &self, + submitter: impl MetricsPingSubmitter + Send + 'static, + now: DateTime, + when: When, + ); +} + +/// Uses Glean to submit "metrics" pings directly. +struct GleanMetricsPingSubmitter {} +impl MetricsPingSubmitter for GleanMetricsPingSubmitter { + fn submit_metrics_ping(&self, glean: &Glean, reason: Option<&str>, now: DateTime) { + glean.submit_ping_by_name("metrics", reason); + // Always update the collection date, irrespective of the ping being sent. + get_last_sent_time_metric().set(&glean, Some(now)); + } +} + +/// Schedule "metrics" pings directly using the default behaviour. +struct GleanMetricsPingScheduler {} +impl MetricsPingScheduler for GleanMetricsPingScheduler { + fn start_scheduler( + &self, + submitter: impl MetricsPingSubmitter + Send + 'static, + now: DateTime, + when: When, + ) { + start_scheduler(submitter, now, when); + } +} + +/// Performs startup checks to decide when to schedule the next "metrics" ping collection. +/// **Must** be called before draining the preinit queue. +/// (We're at the Language Bindings' mercy for that) +pub fn schedule(glean: &Glean) { + let now = local_now_with_offset().0; + + let (cancelled_lock, _condvar) = &**TASK_CONDVAR; + if *cancelled_lock.lock().unwrap() { + log::debug!("Told to schedule, but already cancelled. Are we in a test?"); + } + *cancelled_lock.lock().unwrap() = false; // Uncancel the thread. + + let submitter = GleanMetricsPingSubmitter {}; + let scheduler = GleanMetricsPingScheduler {}; + + schedule_internal(&glean, submitter, scheduler, now) +} + +/// Tells the scheduler task to exit quickly and cleanly. +pub fn cancel() { + let (cancelled_lock, condvar) = &**TASK_CONDVAR; // One `*` for Lazy, the second for Arc + *cancelled_lock.lock().unwrap() = true; // Cancel the scheduler thread. + condvar.notify_all(); // Notify any/all listening schedulers to check whether they were cancelled. +} + +fn schedule_internal( + glean: &Glean, + submitter: impl MetricsPingSubmitter + Send + 'static, + scheduler: impl MetricsPingScheduler, + now: DateTime, +) { + let last_sent_build_metric = get_last_sent_build_metric(); + if let Some(last_sent_build) = last_sent_build_metric.get_value(&glean, INTERNAL_STORAGE) { + // If `app_build` is longer than StringMetric's max length, we will always + // treat it as a changed build when really it isn't. + // This will be externally-observable as InvalidOverflow errors on both the core + // `client_info.app_build` metric and the scheduler's internal metric. + if last_sent_build != glean.app_build { + last_sent_build_metric.set(&glean, &glean.app_build); + log::info!("App build changed. Sending 'metrics' ping"); + submitter.submit_metrics_ping(&glean, Some("upgrade"), now); + scheduler.start_scheduler(submitter, now, When::Reschedule); + return; + } + } + + let last_sent_time = get_last_sent_time_metric().get_value(&glean, INTERNAL_STORAGE); + if let Some(last_sent) = last_sent_time { + log::info!("The 'metrics' ping was last sent on {}", last_sent); + } + + // We aim to cover 3 cases here: + // + // 1. The ping was already collected on the current calendar day; + // only schedule one for collection on the next calendar day at the due time. + // 2. The ping was NOT collected on the current calendar day AND we're later + // than today's due time; collect the ping immediately. + // 3. The ping was NOT collected on the current calendar day BUT we still have + // some time to the due time; schedule for submitting the current calendar day. + + let already_sent_today = last_sent_time.map_or(false, |d| d.date() == now.date()); + if already_sent_today { + // Case #1 + log::info!("The 'metrics' ping was already sent today, {}", now); + scheduler.start_scheduler(submitter, now, When::Tomorrow); + } else if now > now.date().and_hms(SCHEDULED_HOUR, 0, 0) { + // Case #2 + log::info!("Sending the 'metrics' ping immediately, {}", now); + submitter.submit_metrics_ping(&glean, Some("overdue"), now); + scheduler.start_scheduler(submitter, now, When::Reschedule); + } else { + // Case #3 + log::info!("The 'metrics' collection is scheduled for today, {}", now); + scheduler.start_scheduler(submitter, now, When::Today); + } +} + +/// "metrics" ping scheduling deadlines. +#[derive(Debug, PartialEq)] +enum When { + Today, + Tomorrow, + Reschedule, +} + +impl When { + /// Returns the duration from now until our deadline. + /// Note that std::time::Duration doesn't do negative time spans, so if + /// our deadline has passed, this will return zero. + fn until(&self, now: DateTime) -> std::time::Duration { + let fire_date = match self { + Self::Today => now.date().and_hms(SCHEDULED_HOUR, 0, 0), + // Doesn't actually save us from being an hour off on DST because + // chrono doesn't know when DST changes. : ( + Self::Tomorrow | Self::Reschedule => { + (now.date() + Duration::days(1)).and_hms(SCHEDULED_HOUR, 0, 0) + } + }; + // After rust-lang/rust#73544 can use std::time::Duration::ZERO + (fire_date - now) + .to_std() + .unwrap_or_else(|_| std::time::Duration::from_millis(0)) + } + + /// The "metrics" ping reason corresponding to our deadline. + fn reason(&self) -> &'static str { + match self { + Self::Today => "today", + Self::Tomorrow => "tomorrow", + Self::Reschedule => "reschedule", + } + } +} + +fn start_scheduler( + submitter: impl MetricsPingSubmitter + Send + 'static, + now: DateTime, + when: When, +) -> JoinHandle<()> { + let pair = Arc::clone(&TASK_CONDVAR); + std::thread::Builder::new() + .name("glean.mps".into()) + .spawn(move || { + let (cancelled_lock, condvar) = &*pair; + let mut when = when; + let mut now = now; + loop { + let dur = when.until(now); + log::info!("Scheduling for {:?} after {}, reason {:?}", dur, now, when); + let result = condvar.wait_timeout_while(cancelled_lock.lock().unwrap(), dur, |cancelled| !*cancelled); + now = local_now_with_offset().0; + match result { + Err(err) => { + log::warn!("Condvar wait failure, {}", err); + break; + } + Ok((cancelled, wait_result)) => { + if *cancelled { + log::info!("Metrics Ping Scheduler cancelled. Exiting."); + break; + } else if wait_result.timed_out() { + log::info!("Time to submit our metrics ping, {:?}", when); + let glean = crate::global_glean().expect("Global Glean not present when trying to send scheduled 'metrics' ping?!").lock().unwrap(); + submitter.submit_metrics_ping(&glean, Some(when.reason()), now); + when = When::Reschedule; + } else { + // This should be impossible. `cancelled_lock` is acquired, and + // `!*cancelled` is checked by the condvar before it is allowed + // to return from `wait_timeout_while` (I checked). + // So `Ok(_)` implies `*cancelled || wait_result.timed_out`. + log::warn!("Spurious wakeup of the MPS condvar should be impossible."); + } + } + } + } + }).expect("Unable to spawn Metrics Ping Scheduler thread.") +} + +fn get_last_sent_time_metric() -> DatetimeMetric { + DatetimeMetric::new( + CommonMetricData { + name: "last_sent_time".into(), + category: "mps".into(), + send_in_pings: vec![INTERNAL_STORAGE.into()], + lifetime: Lifetime::User, + ..Default::default() + }, + TimeUnit::Minute, + ) +} + +fn get_last_sent_build_metric() -> StringMetric { + StringMetric::new(CommonMetricData { + name: "last_sent_build".into(), + category: "mps".into(), + send_in_pings: vec![INTERNAL_STORAGE.into()], + lifetime: Lifetime::User, + ..Default::default() + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::tests::new_glean; + use std::sync::atomic::{AtomicU32, Ordering}; + + struct ValidatingSubmitter, Option<&str>)> { + submit_validator: F, + validator_run_count: Arc, + } + struct ValidatingScheduler, When)> { + schedule_validator: F, + validator_run_count: Arc, + } + impl, Option<&str>)> MetricsPingSubmitter for ValidatingSubmitter { + fn submit_metrics_ping( + &self, + _glean: &Glean, + reason: Option<&str>, + now: DateTime, + ) { + (self.submit_validator)(now, reason); + self.validator_run_count.fetch_add(1, Ordering::Relaxed); + } + } + impl, When)> MetricsPingScheduler for ValidatingScheduler { + fn start_scheduler( + &self, + _submitter: impl MetricsPingSubmitter + Send + 'static, + now: DateTime, + when: When, + ) { + (self.schedule_validator)(now, when); + self.validator_run_count.fetch_add(1, Ordering::Relaxed); + } + } + + fn new_proxies< + F1: Fn(DateTime, Option<&str>), + F2: Fn(DateTime, When), + >( + submit_validator: F1, + schedule_validator: F2, + ) -> ( + ValidatingSubmitter, + Arc, + ValidatingScheduler, + Arc, + ) { + let submitter_count = Arc::new(AtomicU32::new(0)); + let submitter = ValidatingSubmitter { + submit_validator, + validator_run_count: Arc::clone(&submitter_count), + }; + let scheduler_count = Arc::new(AtomicU32::new(0)); + let scheduler = ValidatingScheduler { + schedule_validator, + validator_run_count: Arc::clone(&scheduler_count), + }; + (submitter, submitter_count, scheduler, scheduler_count) + } + + // Ensure that if we have a different build, we immediately submit an "upgrade" ping + // and schedule a "reschedule" ping for tomorrow. + #[test] + fn different_app_builds_submit_and_reschedule() { + let (mut glean, _t) = new_glean(None); + + glean.app_build = "a build".into(); + get_last_sent_build_metric().set(&glean, "a different build"); + + let (submitter, submitter_count, scheduler, scheduler_count) = new_proxies( + |_, reason| assert_eq!(reason, Some("upgrade")), + |_, when| assert_eq!(when, When::Reschedule), + ); + + schedule_internal(&glean, submitter, scheduler, local_now_with_offset().0); + assert_eq!(1, submitter_count.swap(0, Ordering::Relaxed)); + assert_eq!(1, scheduler_count.swap(0, Ordering::Relaxed)); + } + + // If we've already sent a ping today, ensure we don't send a ping but we + // do schedule a ping for tomorrow. ("Case #1" in schedule_internal) + #[test] + fn case_1_no_submit_but_schedule_tomorrow() { + let (glean, _t) = new_glean(None); + + let fake_now = FixedOffset::east(0).ymd(2021, 4, 30).and_hms(14, 36, 14); + get_last_sent_time_metric().set(&glean, Some(fake_now)); + + let (submitter, submitter_count, scheduler, scheduler_count) = new_proxies( + |_, reason| panic!("Case #1 shouldn't submit a ping! reason: {:?}", reason), + |_, when| assert_eq!(when, When::Tomorrow), + ); + schedule_internal(&glean, submitter, scheduler, fake_now); + assert_eq!(0, submitter_count.swap(0, Ordering::Relaxed)); + assert_eq!(1, scheduler_count.swap(0, Ordering::Relaxed)); + } + + // If we haven't sent a ping today and we're after the scheduled time, + // ensure we send a ping and then schedule a "reschedule" ping for tomorrow. + // ("Case #2" in schedule_internal) + #[test] + fn case_2_submit_ping_and_reschedule() { + let (glean, _t) = new_glean(None); + + let fake_yesterday = FixedOffset::east(0) + .ymd(2021, 4, 29) + .and_hms(SCHEDULED_HOUR, 0, 1); + get_last_sent_time_metric().set(&glean, Some(fake_yesterday)); + let fake_now = fake_yesterday + Duration::days(1); + + let (submitter, submitter_count, scheduler, scheduler_count) = new_proxies( + |_, reason| assert_eq!(reason, Some("overdue")), + |_, when| assert_eq!(when, When::Reschedule), + ); + schedule_internal(&glean, submitter, scheduler, fake_now); + assert_eq!(1, submitter_count.swap(0, Ordering::Relaxed)); + assert_eq!(1, scheduler_count.swap(0, Ordering::Relaxed)); + } + + // If we haven't sent a ping today and we're before the scheduled time, + // ensure we don't send a ping but schedule a "today" ping for today. + // ("Case #3" in schedule_internal) + #[test] + fn case_3_no_submit_but_schedule_today() { + let (glean, _t) = new_glean(None); + + let fake_yesterday = + FixedOffset::east(0) + .ymd(2021, 4, 29) + .and_hms(SCHEDULED_HOUR - 1, 0, 1); + get_last_sent_time_metric().set(&glean, Some(fake_yesterday)); + let fake_now = fake_yesterday + Duration::days(1); + + let (submitter, submitter_count, scheduler, scheduler_count) = new_proxies( + |_, reason| panic!("Case #3 shouldn't submit a ping! reason: {:?}", reason), + |_, when| assert_eq!(when, When::Today), + ); + schedule_internal(&glean, submitter, scheduler, fake_now); + assert_eq!(0, submitter_count.swap(0, Ordering::Relaxed)); + assert_eq!(1, scheduler_count.swap(0, Ordering::Relaxed)); + } + + // `When` is responsible for date math. Let's make sure it's correct. + #[test] + fn when_gets_at_least_some_date_math_correct() { + let now = FixedOffset::east(0).ymd(2021, 4, 30).and_hms(15, 2, 10); + // `now` is after `SCHEDULED_HOUR` so should be zero: + assert_eq!(std::time::Duration::from_secs(0), When::Today.until(now)); + // If we bring it back before `SCHEDULED_HOUR` it should give us the duration: + let earlier = now.date().and_hms(SCHEDULED_HOUR - 1, 0, 0); + assert_eq!( + std::time::Duration::from_secs(3600), + When::Today.until(earlier) + ); + + // `Tomorrow` and `Reschedule` should differ only in their `reason()` + // 46670s is 12h57m10s (aka, the time from 15:02:10 to 04:00:00 + // (when the timezone doesn't change between them)). + assert_eq!( + std::time::Duration::from_secs(46670), + When::Tomorrow.until(now) + ); + assert_eq!( + std::time::Duration::from_secs(46670), + When::Reschedule.until(now) + ); + assert_eq!(When::Tomorrow.until(now), When::Reschedule.until(now)); + assert_ne!(When::Tomorrow.reason(), When::Reschedule.reason()); + } + + // Scheduler tests mutate global state and thus must not be run in parallel. + // Otherwise one test could cancel the other. + // This Mutex aims to solve that. + static SCHEDULER_TEST_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); + + // The scheduler has been designed to be cancellable. Can we cancel it? + #[test] + fn cancellable_tasks_can_be_cancelled() { + // First and foremost, all scheduler tests must ensure they start uncancelled. + // Perils of having shared state. + let _test_lock = SCHEDULER_TEST_MUTEX.lock().unwrap(); + let (cancelled_lock, _condvar) = &**TASK_CONDVAR; // One `*` for Lazy, the second for Arc + *cancelled_lock.lock().unwrap() = false; + + // Pick a time at least two hours from the next scheduled submission. + // (So that this test will time out if cancellation fails). + let now = FixedOffset::east(0) + .ymd(2021, 4, 30) + .and_hms(SCHEDULED_HOUR - 2, 0, 0); + + let proxy_factory = || { + new_proxies( + |_, reason| { + panic!( + "Shouldn't submit when testing scheduler. reason: {:?}", + reason + ) + }, + |_, _| panic!("Not even using the scheduler this time."), + ) + }; + + // Test Today. + let (submitter, submitter_count, _, _) = proxy_factory(); + let handle = start_scheduler(submitter, now, When::Today); + super::cancel(); + handle.join().unwrap(); // Should complete immediately. + assert_eq!(0, submitter_count.swap(0, Ordering::Relaxed)); + + // Test Tomorrow. + let (submitter, submitter_count, _, _) = proxy_factory(); + *cancelled_lock.lock().unwrap() = false; // Uncancel. + let handle = start_scheduler(submitter, now, When::Tomorrow); + super::cancel(); + handle.join().unwrap(); // Should complete immediately. + assert_eq!(0, submitter_count.swap(0, Ordering::Relaxed)); + + // Test Reschedule. + let (submitter, submitter_count, _, _) = proxy_factory(); + *cancelled_lock.lock().unwrap() = false; // Uncancel. + let handle = start_scheduler(submitter, now, When::Reschedule); + super::cancel(); + handle.join().unwrap(); // Should complete immediately. + assert_eq!(0, submitter_count.swap(0, Ordering::Relaxed)); + } + + // We're not keen to wait like the scheduler is, but we can test a quick schedule. + #[test] + fn immediate_task_runs_immediately() { + // First and foremost, all scheduler tests must ensure they start uncancelled. + // Perils of having shared state. + let _test_lock = SCHEDULER_TEST_MUTEX.lock().unwrap(); + let (cancelled_lock, _condvar) = &**TASK_CONDVAR; // One `*` for Lazy, the second for Arc + *cancelled_lock.lock().unwrap() = false; + + // We're actually going to submit a ping from the scheduler, which requires a global glean. + let (glean, _t) = new_glean(None); + assert!( + !glean.schedule_metrics_pings, + "Real schedulers not allowed in tests!" + ); + assert!(crate::setup_glean(glean).is_ok()); + + // We're choosing a time after SCHEDULED_HOUR so `When::Today` will give us a duration of 0. + let now = FixedOffset::east(0).ymd(2021, 4, 20).and_hms(15, 42, 0); + + let (submitter, submitter_count, _, _) = new_proxies( + move |_, reason| { + assert_eq!(reason, Some("today")); + // After submitting the ping we expect, let's cancel this scheduler so the thread exits. + // (But do it on another thread because the condvar loop is currently holding `cancelled`'s mutex) + std::thread::spawn(super::cancel); + }, + |_, _| panic!("Not using the scheduler this time."), + ); + + let handle = start_scheduler(submitter, now, When::Today); + handle.join().unwrap(); + assert_eq!(1, submitter_count.swap(0, Ordering::Relaxed)); + } +} diff --git a/glean-core/src/upload/directory.rs b/glean-core/src/upload/directory.rs index 6b4a58156b..6c7722aabd 100644 --- a/glean-core/src/upload/directory.rs +++ b/glean-core/src/upload/directory.rs @@ -307,7 +307,7 @@ mod test { glean.register_ping_type(&ping_type); // Submit the ping to populate the pending_pings directory - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); let directory_manager = PingDirectoryManager::new(dir.path()); @@ -333,7 +333,7 @@ mod test { glean.register_ping_type(&ping_type); // Submit the ping to populate the pending_pings directory - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); let directory_manager = PingDirectoryManager::new(&dir.path()); @@ -368,7 +368,7 @@ mod test { glean.register_ping_type(&ping_type); // Submit the ping to populate the pending_pings directory - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); let directory_manager = PingDirectoryManager::new(&dir.path()); @@ -399,11 +399,7 @@ mod test { let (glean, dir) = new_glean(None); // Submit a deletion request ping to populate deletion request folder. - glean - .internal_pings - .deletion_request - .submit(&glean, None) - .unwrap(); + glean.internal_pings.deletion_request.submit(&glean, None); let directory_manager = PingDirectoryManager::new(dir.path()); diff --git a/glean-core/src/upload/mod.rs b/glean-core/src/upload/mod.rs index adefc2448b..877acb3d88 100644 --- a/glean-core/src/upload/mod.rs +++ b/glean-core/src/upload/mod.rs @@ -874,14 +874,10 @@ mod test { // Submit the ping multiple times let n = 10; for _ in 0..n { - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); } - glean - .internal_pings - .deletion_request - .submit(&glean, None) - .unwrap(); + glean.internal_pings.deletion_request.submit(&glean, None); // Clear the queue drop(glean.upload_manager.clear_ping_queue()); @@ -907,7 +903,7 @@ mod test { // Submit the ping multiple times let n = 10; for _ in 0..n { - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); } // Create a new upload manager pointing to the same data_path as the glean instance. @@ -935,7 +931,7 @@ mod test { glean.register_ping_type(&ping_type); // Submit a ping - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); // Get the pending ping directory path let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY); @@ -965,7 +961,7 @@ mod test { glean.register_ping_type(&ping_type); // Submit a ping - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); // Get the pending ping directory path let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY); @@ -995,7 +991,7 @@ mod test { glean.register_ping_type(&ping_type); // Submit a ping - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); // Get the submitted PingRequest match glean.get_upload_task() { @@ -1027,7 +1023,7 @@ mod test { glean.register_ping_type(&ping_type); // Submit a ping - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); // Get the pending ping directory path let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY); @@ -1104,7 +1100,7 @@ mod test { glean.register_ping_type(&ping_type); // Submit a ping - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); // Get the submitted PingRequest match glean.get_upload_task() { @@ -1152,7 +1148,7 @@ mod test { // Submit the ping multiple times let n = 5; for _ in 0..n { - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); } let mut upload_manager = PingUploadManager::no_policy(dir.path()); @@ -1200,7 +1196,7 @@ mod test { // Submit the ping multiple times let n = 10; for _ in 0..n { - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); } let directory_manager = PingDirectoryManager::new(dir.path()); @@ -1272,7 +1268,7 @@ mod test { // Submit the ping multiple times for _ in 0..n { - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); } let directory_manager = PingDirectoryManager::new(dir.path()); @@ -1343,7 +1339,7 @@ mod test { // Submit the ping multiple times for _ in 0..n { - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); } let directory_manager = PingDirectoryManager::new(dir.path()); @@ -1417,7 +1413,7 @@ mod test { // Submit the ping multiple times for _ in 0..n { - glean.submit_ping(&ping_type, None).unwrap(); + glean.submit_ping(&ping_type, None); } let directory_manager = PingDirectoryManager::new(dir.path()); diff --git a/glean-core/tests/common/mod.rs b/glean-core/tests/common/mod.rs index 259d3d1a94..0d85adb281 100644 --- a/glean-core/tests/common/mod.rs +++ b/glean-core/tests/common/mod.rs @@ -56,6 +56,8 @@ pub fn new_glean(tempdir: Option) -> (Glean, tempfile::TempDi upload_enabled: true, max_events: None, delay_ping_lifetime_io: false, + app_build: "unknown".into(), + use_core_mps: false, }; let glean = Glean::new(cfg).unwrap(); diff --git a/glean-core/tests/ping.rs b/glean-core/tests/ping.rs index d0437bf963..11f84bc09b 100644 --- a/glean-core/tests/ping.rs +++ b/glean-core/tests/ping.rs @@ -25,7 +25,7 @@ fn write_ping_to_disk() { }); counter.add(&glean, 1); - assert!(ping.submit(&glean, None).unwrap()); + assert!(ping.submit(&glean, None)); assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len()); } @@ -46,7 +46,7 @@ fn disabling_upload_clears_pending_pings() { }); counter.add(&glean, 1); - assert!(ping.submit(&glean, None).unwrap()); + assert!(ping.submit(&glean, None)); assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len()); // At this point no deletion_request ping should exist // (that is: it's directory should not exist at all) @@ -69,7 +69,7 @@ fn disabling_upload_clears_pending_pings() { assert_eq!(0, get_queued_pings(glean.get_data_path()).unwrap().len()); counter.add(&glean, 1); - assert!(ping.submit(&glean, None).unwrap()); + assert!(ping.submit(&glean, None)); assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len()); } @@ -111,11 +111,11 @@ fn empty_pings_with_flag_are_sent() { // No data is stored in either of the custom pings // Sending this should succeed. - assert_eq!(true, ping1.submit(&glean, None).unwrap()); + assert!(ping1.submit(&glean, None)); assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len()); // Sending this should fail. - assert_eq!(false, ping2.submit(&glean, None).unwrap()); + assert!(!ping2.submit(&glean, None)); assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len()); } @@ -152,7 +152,7 @@ fn test_pings_submitted_metric() { }); counter.add(&glean, 1); - assert!(metrics_ping.submit(&glean, None).unwrap()); + assert!(metrics_ping.submit(&glean, None)); // Check recording in the metrics ping assert_eq!( @@ -186,8 +186,8 @@ fn test_pings_submitted_metric() { // This should record a count of 2 baseline pings in the metrics ping, but // it resets each time on the baseline ping, so we should only ever get 1 // baseline ping recorded in the baseline ping itsef. - assert!(baseline_ping.submit(&glean, None).unwrap()); - assert!(baseline_ping.submit(&glean, None).unwrap()); + assert!(baseline_ping.submit(&glean, None)); + assert!(baseline_ping.submit(&glean, None)); // Check recording in the metrics ping assert_eq!( diff --git a/glean-core/tests/ping_maker.rs b/glean-core/tests/ping_maker.rs index ec3e5e1225..8781ad494d 100644 --- a/glean-core/tests/ping_maker.rs +++ b/glean-core/tests/ping_maker.rs @@ -177,7 +177,7 @@ fn clear_pending_pings() { }); metric.set(&glean, true); - assert!(glean.submit_ping(&ping_type, None).is_ok()); + assert!(glean.submit_ping(&ping_type, None)); assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len()); assert!(ping_maker @@ -194,17 +194,17 @@ fn no_pings_submitted_if_upload_disabled() { let ping_type = PingType::new("store1", true, true, vec![]); glean.register_ping_type(&ping_type); - assert!(glean.submit_ping(&ping_type, None).is_ok()); + assert!(glean.submit_ping(&ping_type, None)); assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len()); // Disable upload, then try to sumbit glean.set_upload_enabled(false); - assert!(glean.submit_ping(&ping_type, None).is_ok()); + assert!(!glean.submit_ping(&ping_type, None)); assert_eq!(0, get_queued_pings(glean.get_data_path()).unwrap().len()); // Test again through the direct call - assert!(ping_type.submit(&glean, None).is_ok()); + assert!(!ping_type.submit(&glean, None)); assert_eq!(0, get_queued_pings(glean.get_data_path()).unwrap().len()); } @@ -215,7 +215,7 @@ fn metadata_is_correctly_added_when_necessary() { let ping_type = PingType::new("store1", true, true, vec![]); glean.register_ping_type(&ping_type); - assert!(glean.submit_ping(&ping_type, None).is_ok()); + assert!(glean.submit_ping(&ping_type, None)); let (_, _, metadata) = &get_queued_pings(glean.get_data_path()).unwrap()[0]; let headers = metadata.as_ref().unwrap().get("headers").unwrap(); diff --git a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy index 2e7a920ea5..96c14ac5a1 100644 --- a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy +++ b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy @@ -135,7 +135,15 @@ except: // depending on the variant type: the generated API definitions don't need to be // different due to that. TaskProvider buildConfigProvider = variant.getGenerateBuildConfigProvider() - def originalPackageName = buildConfigProvider.get().getBuildConfigPackageName().get() + def configProvider = buildConfigProvider.get() + def originalPackageName + // In Gradle 6.x `getBuildConfigPackageName` was reaplced by `namespace`. + // We want to be forward compatible, so we check that first or fallback to the old method. + if (configProvider.hasProperty("namespace")) { + originalPackageName = configProvider.namespace.get() + } else { + originalPackageName = configProvider.getBuildConfigPackageName().get() + } def fullNamespace = "${originalPackageName}.GleanMetrics" def generateKotlinAPI = project.task("${TASK_NAME_PREFIX}SourceFor${variant.name.capitalize()}", type: Exec) { @@ -473,7 +481,7 @@ except: void apply(Project project) { isOffline = project.gradle.startParameter.offline - project.ext.glean_version = "37.0.0" + project.ext.glean_version = "38.0.0" // Print the required glean_parser version to the console. This is // offline builds, and is mentioned in the documentation for offline diff --git a/samples/ios/app/metrics.yaml b/samples/ios/app/metrics.yaml index 0585a179fc..604d36fcb4 100644 --- a/samples/ios/app/metrics.yaml +++ b/samples/ios/app/metrics.yaml @@ -27,6 +27,8 @@ browser.engagement: key2: description: "This is key two" expires: 2100-01-01 + no_lint: + - EXPIRATION_DATE_TOO_FAR event_no_keys: type: event @@ -39,6 +41,8 @@ browser.engagement: notification_emails: - CHANGE-ME@example.com expires: 2100-01-01 + no_lint: + - EXPIRATION_DATE_TOO_FAR basic: os: @@ -52,6 +56,8 @@ basic: notification_emails: - CHANGE-ME@example.com expires: 2100-01-01 + no_lint: + - EXPIRATION_DATE_TOO_FAR test: string_list: @@ -70,6 +76,7 @@ test: expires: 2100-01-01 no_lint: - USER_LIFETIME_EXPIRATION + - EXPIRATION_DATE_TOO_FAR counter: type: counter @@ -87,6 +94,7 @@ test: expires: 2100-01-01 no_lint: - USER_LIFETIME_EXPIRATION + - EXPIRATION_DATE_TOO_FAR timespan: type: timespan @@ -101,6 +109,8 @@ test: notification_emails: - CHANGE-ME@example.com expires: 2100-01-01 + no_lint: + - EXPIRATION_DATE_TOO_FAR is_started: type: boolean @@ -118,6 +128,7 @@ test: expires: 2100-01-01 no_lint: - BASELINE_PING + - EXPIRATION_DATE_TOO_FAR custom: counter: @@ -134,6 +145,8 @@ custom: notification_emails: - CHANGE-ME@test-only.com expires: 2100-01-01 + no_lint: + - EXPIRATION_DATE_TOO_FAR legacy_ids: client_id: @@ -149,3 +162,5 @@ legacy_ids: notification_emails: - CHANGE-ME@example.com expires: 2100-01-01 + no_lint: + - EXPIRATION_DATE_TOO_FAR