Merge "COPYBARA_IMPORT=Project import generated by Copybara." into main
diff --git a/docs/contributing/common-tasks.md b/docs/contributing/common-tasks.md
index 43c5421..4a76189 100644
--- a/docs/contributing/common-tasks.md
+++ b/docs/contributing/common-tasks.md
@@ -24,7 +24,7 @@
- Running the file cannot generate any data. There can be only `CREATE PERFETTO {FUNCTION|TABLE|VIEW|MACRO}` statements inside.
- The name of each standard library object needs to start with `{module_name}_` or be prefixed with an underscore(`_`) for internal objects.
- The names must only contain lower and upper case letters and underscores. When a module is included (using the `INCLUDE PERFETTO MODULE`) the internal objects should not be treated as an API.
+ The names must only contain lower and upper case letters and underscores. When a module is included (using the `INCLUDE PERFETTO MODULE`) the internal objects should not be treated as an API.
- Every table or view should have [a schema](/docs/analysis/perfetto-sql-syntax.md#tableview-schema).
### Documentation
@@ -98,11 +98,11 @@
arg_set_id INT
)
AS
-SELECT
- slice_name,
- slice_ts,
- slice_dur,
- thread_name,
+SELECT
+ slice_name,
+ slice_ts,
+ slice_dur,
+ thread_name,
arg_set_id
FROM thread_slices_for_all_launches
WHERE launch_id = $launch_id AND slice_name GLOB $slice_name;
@@ -156,3 +156,17 @@
1. Go to `protos/perfetto/trace_processor/trace_processor.proto`
2. Increment `TRACE_PROCESSOR_CURRENT_API_VERSION`
3. Add a comment explaining what has changed.
+
+## Update statsd descriptor
+
+Perfetto has limited support for statsd atoms it does not know about.
+
+* Must be referred to using `raw_atom_id` in the config.
+* Show up as `atom_xxx.field_yyy` in trace processor.
+* Only top level messages are parsed.
+
+To update Perfetto's descriptor and handle new atoms from AOSP without these
+limitations:
+
+1. Run `tools/update-statsd-descriptor`.
+2. Upload and land your change as normal.
diff --git a/infra/perfetto.dev/src/assets/script.js b/infra/perfetto.dev/src/assets/script.js
index 44adbf6..6c4d04d 100644
--- a/infra/perfetto.dev/src/assets/script.js
+++ b/infra/perfetto.dev/src/assets/script.js
@@ -69,7 +69,7 @@
toc.appendChild(li);
doAfterLoadEvent(() => {
tocAnchors.push(
- {top: anchor.offsetTop + anchor.offsetHeight / 2, obj: link});
+ { top: anchor.offsetTop + anchor.offsetHeight / 2, obj: link });
});
}
tocContainer.innerHTML = '';
@@ -81,7 +81,7 @@
return;
tocEventHandlersInstalled = true;
const doc = document.querySelector('.doc');
- const passive = {passive: true};
+ const passive = { passive: true };
if (doc) {
const offY = doc.offsetTop;
doc.addEventListener('mousemove', (e) => onMouseMove(offY, e), passive);
@@ -91,9 +91,9 @@
}
window.addEventListener('scroll', () => onScroll(), passive);
resizeObserver = new ResizeObserver(() => requestAnimationFrame(() => {
- updateNav();
- updateTOC();
- }));
+ updateNav();
+ updateTOC();
+ }));
resizeObserver.observe(doc);
}
@@ -235,7 +235,7 @@
function setupSearch() {
const URL =
- 'https://www.googleapis.com/customsearch/v1?key=AIzaSyBTD2XJkQkkuvDn76LSftsgWOkdBz9Gfwo&cx=007128963598137843411:8suis14kcmy&q='
+ 'https://www.googleapis.com/customsearch/v1?key=AIzaSyBTD2XJkQkkuvDn76LSftsgWOkdBz9Gfwo&cx=007128963598137843411:8suis14kcmy&q='
const searchContainer = document.getElementById('search');
const searchBox = document.getElementById('search-box');
const searchRes = document.getElementById('search-res')
diff --git a/infra/perfetto.dev/src/assets/style.scss b/infra/perfetto.dev/src/assets/style.scss
index 63d6fc1..0a0ff2e 100644
--- a/infra/perfetto.dev/src/assets/style.scss
+++ b/infra/perfetto.dev/src/assets/style.scss
@@ -15,234 +15,235 @@
// Common + CSS reset
// -----------------------------------------------------------------------------
:root {
- --site-header-height: 50px;
- --home-highlights-height: 128px;
- --content-max-width: 1100px;
- --anim-ease: cubic-bezier(0.4, 0.0, 0.2, 1);
+ --site-header-height: 50px;
+ --home-highlights-height: 128px;
+ --content-max-width: 1100px;
+ --anim-ease: cubic-bezier(0.4, 0, 0.2, 1);
- // This is set to 1 by JS after onload. This is to prevent flickering on
- // page load on the nav bar and other entries while transitioning in their
- // initial state.
- --anim-enabled: 0;
+ // This is set to 1 by JS after onload. This is to prevent flickering on
+ // page load on the nav bar and other entries while transitioning in their
+ // initial state.
+ --anim-enabled: 0;
- --anim-time: calc(0.15s * var(--anim-enabled));
+ --anim-time: calc(0.15s * var(--anim-enabled));
}
$wide: "(max-width: 1100px)";
$mobile: "(max-width: 768px)";
@mixin minimal-scrollbar {
- &::-webkit-scrollbar {
- width: 8px;
- background-color: transparent;
- }
- &::-webkit-scrollbar-thumb {
- background-color: #ccc;
- border-radius: 8px;
- }
+ &::-webkit-scrollbar {
+ width: 8px;
+ background-color: transparent;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #ccc;
+ border-radius: 8px;
+ }
}
@media (max-aspect-ratio: 1/1) {
- :root {
- --home-highlights-height: 256px;
- }
+ :root {
+ --home-highlights-height: 256px;
+ }
}
* {
- box-sizing: border-box;
- -webkit-tap-highlight-color: none;
+ box-sizing: border-box;
+ -webkit-tap-highlight-color: none;
}
html {
- font-family: Roboto, sans-serif;
- -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ -webkit-font-smoothing: antialiased;
}
html,
body {
- padding: 0;
- margin: 0;
+ padding: 0;
+ margin: 0;
}
h1,
h2,
h3 {
- font-family: inherit;
- font-size: inherit;
- font-weight: inherit;
- padding: 0;
- margin: 0;
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ padding: 0;
+ margin: 0;
}
// -----------------------------------------------------------------------------
// Site header
// -----------------------------------------------------------------------------
.site-header {
- background-color: hsl(210, 30%, 16%);
- color: hsl(210, 17%, 98%);
- position: sticky; // Sticky so the .docs element below doesn't start @ 0.
+ background-color: hsl(210, 30%, 16%);
+ color: hsl(210, 17%, 98%);
+ position: sticky; // Sticky so the .docs element below doesn't start @ 0.
+ top: 0;
+ width: 100%;
+ --sh-padding-y: 5px;
+ max-height: var(--site-header-height);
+ padding: var(--sh-padding-y) 30px;
+ box-shadow: rgba(0, 0, 0, 0.3) 0 3px 3px 0;
+ overflow: hidden;
+ display: flex;
+ z-index: 10;
+ transition: max-height var(--anim-ease) var(--anim-time);
+ &.expanded {
+ max-height: 100vh;
+ }
+ .brand {
+ img {
+ height: 40px;
+ vertical-align: bottom;
+ }
+ font-weight: 200;
+ font-size: 28px;
+ flex-grow: 1;
+ .brand-docs {
+ text-transform: uppercase;
+ font-size: 14px;
+ color: #ecba2a;
+ vertical-align: bottom;
+ line-height: 30px;
+ font-weight: 400;
+ }
+ }
+ > *:not(:first-child) {
+ line-height: calc(var(--site-header-height) - var(--sh-padding-y) * 2);
+ font-family: "Source Sans Pro", sans-serif;
+ font-weight: 400;
+ font-size: 1.1rem;
+ margin: 0 20px;
+ color: hsl(210, 17%, 85%);
+ }
+ a {
+ text-decoration: none;
+ &:hover {
+ color: hsl(210, 17%, 100%);
+ }
+ }
+ .menu {
+ visibility: hidden;
+ font-family: "Material Icons Round";
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ right: 0;
top: 0;
- width: 100%;
- --sh-padding-y: 5px;
- max-height: var(--site-header-height);
- padding: var(--sh-padding-y) 30px;
- box-shadow: rgba(0, 0, 0, 0.3) 0 3px 3px 0;
- overflow: hidden;
- display: flex;
- z-index: 10;
- transition: max-height var(--anim-ease) var(--anim-time);
- &.expanded {
- max-height: 100vh;
- }
- .brand {
- img {
- height: 40px;
- vertical-align: bottom;
- }
- font-weight: 200;
- font-size: 28px;
- flex-grow: 1;
- .brand-docs {
- text-transform: uppercase;
- font-size: 14px;
- color: #ecba2a;
- vertical-align: bottom;
- line-height: 30px;
- font-weight: 400;
- }
- }
- >*:not(:first-child) {
- line-height: calc(var(--site-header-height) - var(--sh-padding-y) * 2);
- font-family: 'Source Sans Pro', sans-serif;
- font-weight: 400;
- font-size: 1.1rem;
- margin: 0 20px;
- color: hsl(210, 17%, 85%);
- }
- a {
- text-decoration: none;
- &:hover {
- color: hsl(210, 17%, 100%);
- }
+ line-height: var(--site-header-height);
+ }
+
+ @media #{$mobile} {
+ flex-direction: column;
+ > *:not(:first-child) {
+ margin-left: 40px;
}
.menu {
- visibility: hidden;
- font-family: 'Material Icons Round';
- font-size: 24px;
- text-align: center;
- position: absolute;
- right: 0;
- top: 0;
- line-height: var(--site-header-height);
+ visibility: visible;
}
-
- @media #{$mobile} {
- flex-direction: column;
- >*:not(:first-child) {
- margin-left: 40px;
- }
- .menu {
- visibility: visible;
- }
- }
+ }
}
-
#search {
- position: relative;
- flex-grow: 0;
- transition: flex-grow cubic-bezier(1, 0.01, 1, 1) var(--anim-time), background-color ease var(--anim-time);
- padding: 0;
+ position: relative;
+ flex-grow: 0;
+ transition: flex-grow cubic-bezier(1, 0.01, 1, 1) var(--anim-time),
+ background-color ease var(--anim-time);
+ padding: 0;
+ &::before {
+ visibility: hidden;
+ user-select: none;
+ content: "";
+ position: fixed;
+ left: 0;
+ right: 0;
+ top: var(--site-header-height);
+ bottom: 0;
+ z-index: -100;
+ background-color: rgba(255, 255, 255, 0.8);
+ backdrop-filter: blur(3px);
+ opacity: 0;
+ transition: opacity ease var(--anim-time), visibility 0s;
+ }
+ &:focus-within {
+ flex-grow: 1000;
&::before {
- visibility: hidden;
- user-select: none;
- content: '';
- position: fixed;
- left: 0;
- right: 0;
- top: var(--site-header-height);
- bottom: 0;
- z-index: -100;
- background-color: rgba(255, 255, 255, 0.8);
- backdrop-filter: blur(3px);
- opacity: 0;
- transition: opacity ease var(--anim-time), visibility 0s;
-
+ display: block;
+ opacity: 1;
+ visibility: visible;
}
- &:focus-within {
- flex-grow: 1000;
- &::before {
- display: block;
- opacity: 1;
- visibility: visible;
- }
- #search-res {
- display: block;
- }
-
- }
-
- @media #{$mobile} {
- display: none;
- }
-
- #search-box {
- width: 100%;
- height: 32px;
- font-size: 1rem;;
- color: #333;
- background-color: rgba(255, 255, 255, 0.9);
- border: 1px solid #eee;
- border-radius: 2px;
- background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875Z"/></svg>');
- background-repeat: no-repeat;
- background-size: contain;
- padding-left: 40px;
- outline: none;
- &:hover, &:focus {
- background-color: rgba(255, 255, 255, 0.95);
- }
- }
-
#search-res {
- display: none;
- background-color: rgba(255, 255, 255, 1.0);
- border: 1px solid #eee;
- box-shadow: #aaa 0px 1px 5px;
- color: #333;
- line-height: initial;
- margin-top: -4px;
- overflow-x: auto;
- position: fixed;
- top: var(--site-header-height);
- max-height: calc(100vh - var(--site-header-height));
- z-index: 10;
- >div {
- padding: 10px;
- margin: 0;
- &:hover {
- background-color: #f0f0f0;
- }
- }
- .sr-title {
- color: #333;
- font-weight: bold;
- }
- .sr-snippet {
- color: #444;
- font-size: 0.9rem;
- }
+ display: block;
+ }
+ }
- a { text-decoration: none; }
- a:hover { color: initial };
+ @media #{$mobile} {
+ display: none;
+ }
- &:empty {
- visibility: hidden;
- }
+ #search-box {
+ width: 100%;
+ height: 32px;
+ font-size: 1rem;
+ color: #333;
+ background-color: rgba(255, 255, 255, 0.9);
+ border: 1px solid #eee;
+ border-radius: 2px;
+ background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875Z"/></svg>');
+ background-repeat: no-repeat;
+ background-size: contain;
+ padding-left: 40px;
+ outline: none;
+ &:hover,
+ &:focus {
+ background-color: rgba(255, 255, 255, 0.95);
+ }
+ }
+
+ #search-res {
+ display: none;
+ background-color: rgba(255, 255, 255, 1);
+ border: 1px solid #eee;
+ box-shadow: #aaa 0px 1px 5px;
+ color: #333;
+ line-height: initial;
+ margin-top: -4px;
+ overflow-x: auto;
+ position: fixed;
+ top: var(--site-header-height);
+ max-height: calc(100vh - var(--site-header-height));
+ z-index: 10;
+ > div {
+ padding: 10px;
+ margin: 0;
+ &:hover {
+ background-color: #f0f0f0;
+ }
+ }
+ .sr-title {
+ color: #333;
+ font-weight: bold;
+ }
+ .sr-snippet {
+ color: #444;
+ font-size: 0.9rem;
}
-}
+ a {
+ text-decoration: none;
+ }
+ a:hover {
+ color: initial;
+ }
+ &:empty {
+ visibility: hidden;
+ }
+ }
+}
// -----------------------------------------------------------------------------
// Site footer
@@ -250,722 +251,759 @@
// Footer in the index page.
.site-footer {
- background-color: hsl(210, 30%, 16%);
- padding: 1em 0;
- font-size: 14px;
- color: #fff;
- text-align: center;
- ul {
- list-style: none;
- margin: 0;
- padding: 0;
- li {
- display: inline;
- padding: 0 10px;
- &:not(:last-child) {
- border-right: solid 1px #fff;
- }
- }
+ background-color: hsl(210, 30%, 16%);
+ padding: 1em 0;
+ font-size: 14px;
+ color: #fff;
+ text-align: center;
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ li {
+ display: inline;
+ padding: 0 10px;
+ &:not(:last-child) {
+ border-right: solid 1px #fff;
+ }
}
- a,
- a:visited {
- text-decoration: none;
- color: inherit;
- }
- .docs-footer-notice { display: none; }
+ }
+ a,
+ a:visited {
+ text-decoration: none;
+ color: inherit;
+ }
+ .docs-footer-notice {
+ display: none;
+ }
}
// Footer overrides for the /docs/ page.
.docs .site-footer {
- grid-area: footer;
- background: transparent;
- color: #666;
- text-align: left;
- margin: 0 20px;
- padding: 12px 0;
+ grid-area: footer;
+ background: transparent;
+ color: #666;
+ text-align: left;
+ margin: 0 20px;
+ padding: 12px 0;
- .docs-footer-notice {
- padding: 0;
- margin: 0;
- display: block;
- }
+ .docs-footer-notice {
+ padding: 0;
+ margin: 0;
+ display: block;
+ }
- ul { display: none; }
+ ul {
+ display: none;
+ }
}
// -----------------------------------------------------------------------------
// Site content
// -----------------------------------------------------------------------------
.site-content {
- .section-wrapper {
- border-bottom: solid 1px #eee;
- &:nth-child(2n+1) {
- background-color: hsl(210, 17%, 98%);
- }
+ .section-wrapper {
+ border-bottom: solid 1px #eee;
+ &:nth-child(2n + 1) {
+ background-color: hsl(210, 17%, 98%);
}
- section {
+ }
+ section {
+ display: block;
+ position: relative;
+ overflow: hidden;
+ padding: 0 20px;
+ margin: 0 auto;
+ max-width: calc(var(--content-max-width) + 2 * 20px);
+ }
+
+ .banner {
+ height: calc(
+ 100vh - var(--home-highlights-height) - var(--site-header-height)
+ );
+ @media (max-height: 639px) {
+ // If the screen is too short (e.g. smartphone in landscape mode)
+ // move the highlights sections (the four tiles) out of the visible
+ // viewport.
+ height: calc(100vh - var(--site-header-height));
+ }
+ min-height: 25vw;
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 1fr 1fr 5fr;
+ h1,
+ h2 {
+ margin: auto;
+ font-family: "Source Sans Pro", sans-serif;
+ text-align: center;
+ color: hsl(0, 0, 35%);
+ span {
+ white-space: nowrap;
+ }
+ }
+ h1 {
+ font-size: 2.5rem;
+ font-size: calc(min(4rem, 8vw, 6vh));
+ font-weight: 400;
+ padding-top: calc(max(1rem, 2vh));
+ }
+ h2 {
+ font-size: 1.25rem;
+ font-size: calc(min(2rem, 6vw, 4vh));
+ font-weight: 200;
+ padding-top: 10px;
+ }
+ .home-img {
+ padding: 1rem 0;
+ overflow: hidden;
+ position: relative;
+ display: flex;
+ img {
+ max-height: 100%;
+ max-width: 100%;
+ margin: auto;
display: block;
- position: relative;
- overflow: hidden;
- padding: 0 20px;
- margin: 0 auto;
- max-width: calc(var(--content-max-width) + 2 * 20px);
+ }
}
+ }
- .banner {
- height: calc(100vh - var(--home-highlights-height) - var(--site-header-height));
- @media (max-height: 639px) {
- // If the screen is too short (e.g. smartphone in landscape mode)
- // move the highlights sections (the four tiles) out of the visible
- // viewport.
- height: calc(100vh - var(--site-header-height));
+ .home-highlights {
+ &:before {
+ border-top: 1px solid hsl(210, 17%, 90%);
+ }
+ height: var(--home-highlights-height);
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ grid-template-rows: 1fr;
+ background-color: #fff;
+ z-index: 2;
+ @media (max-aspect-ratio: 1/1) {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ > a {
+ color: hsl(0, 0, 20%);
+ font-size: 22px;
+ font-weight: 400;
+ text-align: center;
+ padding: 20px 0;
+ font-family: "Source Sans Pro", sans-serif;
+ text-decoration: none;
+ .icon {
+ background-image: url("/assets/sprite.png");
+ background-repeat: no-repeat;
+ width: 64px;
+ height: 64px;
+ margin: auto;
+ background-size: 256px 128px;
+ filter: grayscale(1);
+ transition: filter ease var(--anim-time);
+ }
+ &:nth-child(1) .icon {
+ background-position: 0 -64px;
+ }
+ &:nth-child(2) .icon {
+ background-position: -64px -64px;
+ }
+ &:nth-child(3) .icon {
+ background-position: -128px -64px;
+ }
+ &:nth-child(4) .icon {
+ background-position: -192px -64px;
+ }
+ &:hover {
+ background-color: hsl(210, 17%, 90%);
+ .icon {
+ filter: grayscale(0);
}
- min-height: 25vw;
- display: grid;
+ }
+ }
+ }
+ .home-section {
+ min-height: calc(min(100vh - var(--site-header-height), 800px));
+ padding: 5% 20px;
+ display: grid;
+ grid-template-rows: 1fr;
+ grid-column-gap: 4vw;
+ > img {
+ grid-area: img;
+ max-width: 100%;
+ max-height: 55vh;
+ margin: auto;
+ margin-top: 40px;
+ }
+ h2,
+ > div {
+ grid-area: content;
+ }
+ h2 {
+ font-family: "Source Sans Pro", sans-serif;
+ font-weight: 600;
+ font-size: 2.5rem;
+ color: #333;
+ text-align: center;
+ }
+ &:nth-child(2n) {
+ grid-template-columns: 5fr 4fr;
+ grid-template-areas: "content img";
+ h2 {
+ padding: 0 0 0 50px;
+ text-align: left;
+ }
+ }
+ &:nth-child(2n + 1) {
+ grid-template-columns: 4fr 5fr;
+ grid-template-areas: "img content";
+ h2 {
+ padding: 0 50px 0 0;
+ text-align: left;
+ }
+ }
+ @media (max-aspect-ratio: 1/1) {
+ padding: 5vh 20px;
+ &:nth-child(n) {
+ grid-template-rows: auto auto;
grid-template-columns: 1fr;
- grid-template-rows: 1fr 1fr 5fr;
- h1,
+ grid-template-areas: "img" "content";
+ grid-row-gap: 30px;
h2 {
- margin: auto;
- font-family: 'Source Sans Pro', sans-serif;
- text-align: center;
- color: hsl(0, 0, 35%);
- span {
- white-space: nowrap;
- }
+ padding: 0;
+ text-align: center;
}
- h1 {
- font-size: 2.5rem;
- font-size: calc(min(4rem, 8vw, 6vh));
- font-weight: 400;
- padding-top: calc(max(1rem, 2vh));
- }
- h2 {
- font-size: 1.25rem;
- font-size: calc(min(2rem, 6vw, 4vh));
- font-weight: 200;
- padding-top: 10px;
- }
- .home-img {
- padding: 1rem 0;
- overflow: hidden;
- position: relative;
- display: flex;
- img {
- max-height: 100%;
- max-width: 100%;
- margin: auto;
- display: block;
- }
- }
+ }
+ > img {
+ padding: 0 10vw;
+ }
}
-
-
- .home-highlights {
- &:before {
- border-top: 1px solid hsl(210, 17%, 90%);
+ div {
+ grid-area: content;
+ .button {
+ display: inline-block;
+ background: #337ab7;
+ font-weight: 500;
+ color: #fff;
+ border-radius: 6px;
+ font-size: 18px;
+ padding: 10px 16px;
+ transition: background-color ease var(--anim-time);
+ text-decoration: none;
+ &:hover {
+ background: #286090;
}
- height: var(--home-highlights-height);
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- grid-template-rows: 1fr;
- background-color: #fff;
- z-index: 2;
- @media (max-aspect-ratio: 1/1) {
- grid-template-columns: repeat(2, 1fr);
- }
- >a {
- color: hsl(0, 0, 20%);
- font-size: 22px;
- font-weight: 400;
- text-align: center;
- padding: 20px 0;
- font-family: 'Source Sans Pro', sans-serif;
- text-decoration: none;
- .icon {
- background-image: url('/assets/sprite.png');
- background-repeat: no-repeat;
- width: 64px;
- height: 64px;
- margin: auto;
- background-size: 256px 128px;
- filter: grayscale(1);
- transition: filter ease var(--anim-time);
- }
- &:nth-child(1) .icon {
- background-position: 0 -64px;
- }
- &:nth-child(2) .icon {
- background-position: -64px -64px;
- }
- &:nth-child(3) .icon {
- background-position: -128px -64px;
- }
- &:nth-child(4) .icon {
- background-position: -192px -64px;
- }
- &:hover {
- background-color: hsl(210, 17%, 90%);
- .icon {
- filter: grayscale(0);
- }
- }
- }
+ }
}
- .home-section {
- min-height: calc(min(100vh - var(--site-header-height), 800px));
- padding: 5% 20px;
- display: grid;
- grid-template-rows: 1fr;
- grid-column-gap: 4vw;
- >img {
- grid-area: img;
- max-width: 100%;
- max-height: 55vh;
- margin: auto;
- margin-top: 40px;
- }
- h2,
- >div {
- grid-area: content;
- }
- h2 {
- font-family: 'Source Sans Pro', sans-serif;
- font-weight: 600;
- font-size: 2.5rem;
- color: #333;
- text-align: center;
- }
- &:nth-child(2n) {
- grid-template-columns: 5fr 4fr;
- grid-template-areas: "content img";
- h2 {
- padding: 0 0 0 50px;
- text-align: left;
- }
- }
- &:nth-child(2n+1) {
- grid-template-columns: 4fr 5fr;
- grid-template-areas: "img content";
- h2 {
- padding: 0 50px 0 0;
- text-align: left;
- }
- }
- @media (max-aspect-ratio: 1/1) {
- padding: 5vh 20px;
- &:nth-child(n) {
- grid-template-rows: auto auto;
- grid-template-columns: 1fr;
- grid-template-areas: "img" "content";
- grid-row-gap: 30px;
- h2 {
- padding: 0;
- text-align: center;
- }
- }
- >img {
- padding: 0 10vw;
- }
- }
- div {
- grid-area: content;
- .button {
- display: inline-block;
- background: #337ab7;
- font-weight: 500;
- color: #fff;
- border-radius: 6px;
- font-size: 18px;
- padding: 10px 16px;
- transition: background-color ease var(--anim-time);
- text-decoration: none;
- &:hover {
- background: #286090;
- }
- }
- }
- .home-item {
- display: grid;
- grid-template-rows: auto auto;
- grid-template-columns: 50px auto;
- grid-template-areas: "img title" "img label";
- grid-column-gap: 20px;
- padding: 20px 30px;
- margin: 10px 0;
- // border: 1px solid #eee;
- font-family: 'Source Sans Pro', sans-serif;
- color: #111111;
- transition: filter var(--anim-ease) var(--anim-time), background-color var(--anim-ease) var(--anim-time), transform var(--anim-ease) var(--anim-time), box-shadow linear var(--anim-time);
- border-radius: 6px;
- filter: opacity(0.6);
- &:hover {
- background-color: hsla(0, 0, 0, 0.02);
- filter: opacity(1);
- transform: scale(1.01);
- }
- >img,
- >i {
- grid-area: img;
- margin: auto;
- font-size: 50px;
- }
- >h3 {
- grid-area: title;
- font-size: 1.25rem;
- line-height: 20px;
- font-weight: 600;
- }
- >p {
- grid-area: label;
- font-size: 1rem;
- font-weight: 400;
- margin: 1em 0;
- }
- }
+ .home-item {
+ display: grid;
+ grid-template-rows: auto auto;
+ grid-template-columns: 50px auto;
+ grid-template-areas: "img title" "img label";
+ grid-column-gap: 20px;
+ padding: 20px 30px;
+ margin: 10px 0;
+ // border: 1px solid #eee;
+ font-family: "Source Sans Pro", sans-serif;
+ color: #111111;
+ transition: filter var(--anim-ease) var(--anim-time),
+ background-color var(--anim-ease) var(--anim-time),
+ transform var(--anim-ease) var(--anim-time),
+ box-shadow linear var(--anim-time);
+ border-radius: 6px;
+ filter: opacity(0.6);
+ &:hover {
+ background-color: hsla(0, 0, 0, 0.02);
+ filter: opacity(1);
+ transform: scale(1.01);
+ }
+ > img,
+ > i {
+ grid-area: img;
+ margin: auto;
+ font-size: 50px;
+ }
+ > h3 {
+ grid-area: title;
+ font-size: 1.25rem;
+ line-height: 20px;
+ font-weight: 600;
+ }
+ > p {
+ grid-area: label;
+ font-size: 1rem;
+ font-weight: 400;
+ margin: 1em 0;
+ }
}
+ }
}
// -----------------------------------------------------------------------------
// Docs
// -----------------------------------------------------------------------------
.docs {
- min-height: 100vh;
- display: grid;
- --nav-width: 240px;
- --toc-width: 180px;
+ min-height: 100vh;
+ display: grid;
+ --nav-width: 240px;
+ --toc-width: 180px;
- // 1665px is the clientWidth on a macbook pro. Adjust the layout so that
- // the max-width of the central .doc fits precisely when the browser is
- // full-screen on a macbook.
- --max-doc-width: calc(1665px - var(--toc-width) - var(--nav-width));
+ // 1665px is the clientWidth on a macbook pro. Adjust the layout so that
+ // the max-width of the central .doc fits precisely when the browser is
+ // full-screen on a macbook.
+ --max-doc-width: calc(1665px - var(--toc-width) - var(--nav-width));
- grid-template-columns: var(--nav-width) minmax(auto, var(--max-doc-width)) var(--toc-width);
- grid-template-rows: 1fr max-content;
- grid-template-areas: "nav doc toc" "nav footer toc";
+ grid-template-columns: var(--nav-width) minmax(auto, var(--max-doc-width)) var(
+ --toc-width
+ );
+ grid-template-rows: 1fr max-content;
+ grid-template-areas: "nav doc toc" "nav footer toc";
- background-color: hsl(210, 10%, 97%);
- .nav {
- grid-area: nav;
- border-right: 1px solid hsl(210, 30%, 90%);
- background-color: #fefefe;
- padding: 20px 0;
- padding-right: 16px;
+ background-color: hsl(210, 10%, 97%);
+ .nav {
+ grid-area: nav;
+ border-right: 1px solid hsl(210, 30%, 90%);
+ background-color: #fefefe;
+ padding: 20px 0;
+ padding-right: 16px;
- position: sticky;
- top: var(--site-header-height);
- height: calc(100vh - var(--site-header-height));
- overflow-y: auto;
- @include minimal-scrollbar;
+ position: sticky;
+ top: var(--site-header-height);
+ height: calc(100vh - var(--site-header-height));
+ overflow-y: auto;
+ @include minimal-scrollbar;
- a {
- color: inherit;
- text-decoration: none;
- line-height: 24px;
- display: flex;
- transition: background-color var(--anim-ease) var(--anim-time),
- visibility linear var(--anim-time);
- border-radius: 0 10px 10px 0;
- -webkit-tap-highlight-color: transparent;
- &[href] {
- &:hover {
- color: #000;
- background-color: #f1f3f4;
- }
- &.selected {
- background-color: #ecba2a;
- }
- }
+ a {
+ color: inherit;
+ text-decoration: none;
+ line-height: 24px;
+ display: flex;
+ transition: background-color var(--anim-ease) var(--anim-time),
+ visibility linear var(--anim-time);
+ border-radius: 0 10px 10px 0;
+ -webkit-tap-highlight-color: transparent;
+ &[href] {
+ &:hover {
+ color: #000;
+ background-color: #f1f3f4;
}
-
- ul {
- list-style: none;
- margin: 0;
- padding: 0;
- overflow: hidden;
- li {
- font-size: 1rem;
- font-weight: 400;
- font-family: 'Source Sans Pro', sans-serif;
- color: #4a4a4a;
- max-width: 100%;
- margin: 3px 0;
- }
- p { margin: 0; }
+ &.selected {
+ background-color: #ecba2a;
}
-
- // Applies only to outer-level submenus.
- >ul {
- position: static; // Otherwise gets v-centered in the middle.
- > li {
- padding-bottom: 10px;
- margin-bottom: 10px;
- font-weight: 600;
- color: #111;
-
- &:not(:last-child) {
- border-bottom: 1px solid #eee;
- }
-
- &.compressible {
- > p > a::after {
- content: 'keyboard_arrow_up';
- font-family: 'Material Icons Round';
- font-size: 24px;
- width: 24px;
- transition: transform var(--anim-ease) var(--anim-time);
- margin: 0 0 0 auto;
- font-weight: 200;
- color: #666;
- }
- > ul {
- transition: max-height var(--anim-ease) var(--anim-time),
- opacity var(--anim-ease) var(--anim-time);
- opacity: 1;
- }
- &.compressed {
- // The JS will compute and set the maxHeight on each
- // element depending on the size of their children.
- // !important is needed to override the element-inline
- // max-height property set by JS, which is prioritary.
- > ul {
- max-height: 0 !important;
- visibility: hidden;
- opacity: 0;
- }
- > p > a::after {
- transform: scaleY(-1);
- }
- }
- } // .compressible
-
- }
- }
-
- li a {
- padding-left: 16px;
- }
- li li a {
- padding-left: 30px;
- }
- li li li a {
- padding-left: 44px;
- }
- .expanded a::after {
- transform: rotate(180deg);
- }
+ }
}
- .doc {
- grid-area: doc;
- background-color: #fff;
- margin: 20px;
- padding: 30px 40px;
- font-family: Roboto, sans-serif;
+
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ li {
font-size: 1rem;
font-weight: 400;
- line-height: 24px;
- -webkit-font-smoothing: antialiased;
+ font-family: "Source Sans Pro", sans-serif;
color: #4a4a4a;
- position: relative;
- box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .1), 0 1px 3px 1px rgba(60, 64, 67, .15);
- overflow: hidden;
+ max-width: 100%;
+ margin: 3px 0;
+ }
+ p {
+ margin: 0;
+ }
+ }
- a {
- text-decoration: none;
- &:link { color: #007b83; }
- &:visited { color: #8e3317; }
- &:hover { color: #009da8; }
- &[href^="http"] {
- // External link.
- &:after {
- content: 'open_in_new';
- font-family: 'Material Icons Round';
- color: #666;
- text-decoration: none;
- margin-left: 2px;
- margin-right: 4px;
- vertical-align: bottom;
- }
- }
+ // Applies only to outer-level submenus.
+ > ul {
+ position: static; // Otherwise gets v-centered in the middle.
+ > li {
+ padding-bottom: 10px;
+ margin-bottom: 10px;
+ font-weight: 600;
+ color: #111;
+
+ &:not(:last-child) {
+ border-bottom: 1px solid #eee;
}
- h1,
- h2,
- h3 {
- margin: 10px 0;
- padding: 0;
- padding-top: 30px;
- }
- h1 {
- font-size: 2.25rem;
- line-height: 2.25rem;
- margin: 0;
- padding: 0;
- margin-bottom: 1.5rem;
- font-family: 'Source Sans Pro', sans-serif;
- }
- h2 {
- font-size: 1.5rem;
- border-bottom: 1px solid #e8eaed;
- padding-bottom: 6px;
- }
- h3 {
- font-size: 1.25rem;
- }
- * {
- max-width: 100%;
- }
-
- img[alt$="screenshot"] {
- box-shadow: 0 0 10px 2px #eee;
- }
-
- code:not(.code-block) {
- background: hsla(210, 17%, 90%, 0.2);
- border: 1px solid #E8EAED;
- border-radius: 6px;
- padding: 1px 4px;
- }
- .code-block {
- overflow-x: auto;
- white-space: pre;
- border-radius: 6px;
- box-shadow: 1px 1px 6px #999;
- border-top: 5px solid #8BC34A;
- }
- // Hide mermaid graphs until they are rendered, this is to avoid showing
- // the mermaid source while the renderer generates the SVG.
- .mermaid {
- transition: opacity var(--anim-ease) var(--anim-time);
- &:not(.rendered) {
- opacity: 0;
- }
- }
- .anchor {
- margin-left: -29px;
- padding-right: 5px;
- text-decoration: none;
- position: absolute;
- padding-top: var(--site-header-height);
- margin-top: calc(-1 * var(--site-header-height));
- outline: none;
- opacity: 0;
- transition: opacity var(--anim-ease) var(--anim-time);
- &::before {
- content: 'insert_link';
- font-family: 'Material Icons Round';
- color: #333;
- font-size: 24px;
- }
- }
- *:hover .anchor {
+ &.compressible {
+ > p > a::after {
+ content: "keyboard_arrow_up";
+ font-family: "Material Icons Round";
+ font-size: 24px;
+ width: 24px;
+ transition: transform var(--anim-ease) var(--anim-time);
+ margin: 0 0 0 auto;
+ font-weight: 200;
+ color: #666;
+ }
+ > ul {
+ transition: max-height var(--anim-ease) var(--anim-time),
+ opacity var(--anim-ease) var(--anim-time);
opacity: 1;
+ }
+ &.compressed {
+ // The JS will compute and set the maxHeight on each
+ // element depending on the size of their children.
+ // !important is needed to override the element-inline
+ // max-height property set by JS, which is prioritary.
+ > ul {
+ max-height: 0 !important;
+ visibility: hidden;
+ opacity: 0;
+ }
+ > p > a::after {
+ transform: scaleY(-1);
+ }
+ }
+ } // .compressible
+ }
+ }
+
+ li a {
+ padding-left: 16px;
+ }
+ li li a {
+ padding-left: 30px;
+ }
+ li li li a {
+ padding-left: 44px;
+ }
+ .expanded a::after {
+ transform: rotate(180deg);
+ }
+ }
+ .doc {
+ grid-area: doc;
+ background-color: #fff;
+ margin: 20px;
+ padding: 30px 40px;
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 24px;
+ -webkit-font-smoothing: antialiased;
+ color: #4a4a4a;
+ position: relative;
+ box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.1),
+ 0 1px 3px 1px rgba(60, 64, 67, 0.15);
+ overflow: hidden;
+
+ a {
+ text-decoration: none;
+ &:link {
+ color: #007b83;
+ }
+ &:visited {
+ color: #8e3317;
+ }
+ &:hover {
+ color: #009da8;
+ }
+ &[href^="http"] {
+ // External link.
+ &:after {
+ content: "open_in_new";
+ font-family: "Material Icons Round";
+ color: #666;
+ text-decoration: none;
+ margin-left: 2px;
+ margin-right: 4px;
+ vertical-align: bottom;
}
+ }
+ }
+
+ h1,
+ h2,
+ h3 {
+ margin: 10px 0;
+ padding: 0;
+ padding-top: 30px;
+ }
+ h1 {
+ font-size: 2.25rem;
+ line-height: 2.25rem;
+ margin: 0;
+ padding: 0;
+ margin-bottom: 1.5rem;
+ font-family: "Source Sans Pro", sans-serif;
+ }
+ h2 {
+ font-size: 1.5rem;
+ border-bottom: 1px solid #e8eaed;
+ padding-bottom: 6px;
+ }
+ h3 {
+ font-size: 1.25rem;
+ }
+ * {
+ max-width: 100%;
+ }
+
+ img[alt$="screenshot"] {
+ box-shadow: 0 0 10px 2px #eee;
+ }
+
+ code:not(.code-block) {
+ background: hsla(210, 17%, 90%, 0.2);
+ border: 1px solid #e8eaed;
+ border-radius: 6px;
+ padding: 1px 4px;
+ }
+ .code-block {
+ overflow-x: auto;
+ white-space: pre;
+ border-radius: 6px;
+ box-shadow: 1px 1px 6px #999;
+ border-top: 5px solid #8bc34a;
+ }
+ // Hide mermaid graphs until they are rendered, this is to avoid showing
+ // the mermaid source while the renderer generates the SVG.
+ .mermaid {
+ transition: opacity var(--anim-ease) var(--anim-time);
+ &:not(.rendered) {
+ opacity: 0;
+ }
+ }
+ .anchor {
+ margin-left: -29px;
+ padding-right: 5px;
+ text-decoration: none;
+ position: absolute;
+ padding-top: var(--site-header-height);
+ margin-top: calc(-1 * var(--site-header-height));
+ outline: none;
+ opacity: 0;
+ transition: opacity var(--anim-ease) var(--anim-time);
+ &::before {
+ content: "insert_link";
+ font-family: "Material Icons Round";
+ color: #333;
+ font-size: 24px;
+ }
+ }
+ *:hover .anchor {
+ opacity: 1;
+ }
+ code {
+ font-family: "Roboto Mono", monospace;
+ font-size: 14px;
+ }
+ table {
+ width: 100%;
+ font-size: 14px;
+ border-spacing: 0;
+ border-collapse: collapse;
+ th,
+ td {
+ padding: 8px;
+ border: 0 solid #dadce0;
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+ }
+ tr {
+ height: 20px;
+ }
+ tr:target {
+ background-color: #ecba2a;
+ }
+ thead {
+ text-align: left;
+ background-color: #e8eaed;
+ color: #202124;
+ }
+ }
+
+ &[data-md-file^="/docs/reference/"] {
+ h1,
+ h2,
+ h3 {
code {
- font-family: 'Roboto Mono', monospace;
- font-size: 14px;
+ margin-left: 20px;
+ color: #666;
}
- table {
- width: 100%;
- font-size: 14px;
- border-spacing: 0;
- border-collapse: collapse;
- th, td {
- padding: 8px;
- border: 0 solid #dadce0;
- border-top-width: 1px;
- border-bottom-width: 1px;
-
- }
- tr {
- height: 20px;
- }
- tr:target {
- background-color: #ecba2a;
- }
- thead {
- text-align: left;
- background-color: #e8eaed;
- color: #202124;
- }
+ }
+ table {
+ width: 100%;
+ font-size: 14px;
+ border-spacing: 0;
+ border-collapse: collapse;
+ th,
+ td {
+ padding: 8px;
+ border: 0 solid #dadce0;
+ border-top-width: 1px;
+ border-bottom-width: 1px;
}
+ tr {
+ height: 20px;
+ }
+ thead {
+ text-align: left;
+ background-color: #e8eaed;
+ color: #202124;
+ }
+ td {
+ &:first-child {
+ background: #f7f7f7;
+ }
- &[data-md-file^="/docs/reference/"] {
- h1, h2, h3 {
- code {
- margin-left: 20px;
- color: #666;
- }
- }
- table {
- width: 100%;
- font-size: 14px;
- border-spacing: 0;
- border-collapse: collapse;
- th, td {
- padding: 8px;
- border: 0 solid #dadce0;
- border-top-width: 1px;
- border-bottom-width: 1px;
-
- }
- tr {
- height: 20px;
- }
- thead {
- text-align: left;
- background-color: #e8eaed;
- color: #202124;
- }
- td {
- &:first-child { background: #f7f7f7; }
-
- /* Not really 100% but makes sure that the description
+ /* Not really 100% but makes sure that the description
* column takes most of the width */
- &:last-child { width: 80%; }
- }
- }
+ &:last-child {
+ width: 80%;
+ }
}
-
- .callout {
- padding: .5rem .5rem .5rem 2rem;
- border: none;
- border-radius: 2px;
- margin-left: auto;
- margin-right: auto;
- width: 90%;
- border-left: 3px solid transparent;
- box-shadow: 0 0.2rem 0.5rem rgba(0,0,0,.05), 0 0 0.05rem rgba(0,0,0,.1);
-
- &:before {
- font-family: 'Material Icons Round';
- position: absolute;
- font-size: 1.5rem;
- margin-left: -1.75rem;
- margin-top: -2px;
- }
-
- &.note {
- background-color: #E8F0FE;
- border-color: #1967D2;
- color: #1967D2;
- &:before { content: 'bookmark'; }
- }
-
- &.summary {
- background-color: #E4F7FB;
- border-color: #129EAF;
- color: #129EAF;
- &:before { content: 'sms'; }
- }
-
- &.tip {
- background-color: #E6F4EA;
- border-color: #188038;
- color: #188038;
- &:before { content: 'star'; }
- }
-
- &.todo {
- background-color: #F1F3F4;
- border-color: #5F6368;
- color: #5F6368;
- &:before { content: 'error'; }
- }
-
- &.warning {
- background-color: #FCE8E6;
- border-color: #C5221F;
- color: #C5221F;
- &:before { content: 'warning'; }
- }
- }
+ }
}
- .toc {
- grid-area: toc;
- padding: 20px 16px 20px 0;
- position: sticky;
- top: var(--site-header-height);
- height: calc(100vh - var(--site-header-height));
- overflow-y: auto;
- @include minimal-scrollbar;
+ .callout {
+ padding: 0.5rem 0.5rem 0.5rem 2rem;
+ border: none;
+ border-radius: 2px;
+ margin-left: auto;
+ margin-right: auto;
+ width: 90%;
+ border-left: 3px solid transparent;
+ box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05),
+ 0 0 0.05rem rgba(0, 0, 0, 0.1);
- font-family: 'Source Sans Pro', sans-serif;
- word-break: break-word;
- a {
- text-decoration: none;
+ &:before {
+ font-family: "Material Icons Round";
+ position: absolute;
+ font-size: 1.5rem;
+ margin-left: -1.75rem;
+ margin-top: -2px;
+ }
+
+ &.note {
+ background-color: #e8f0fe;
+ border-color: #1967d2;
+ color: #1967d2;
+ &:before {
+ content: "bookmark";
}
- a,
- a:visited {
- color: #333;
+ }
+
+ &.summary {
+ background-color: #e4f7fb;
+ border-color: #129eaf;
+ color: #129eaf;
+ &:before {
+ content: "sms";
}
- a.highlighted {
- font-weight: 500;
- color: hsl(45, 100%, 40%);
+ }
+
+ &.tip {
+ background-color: #e6f4ea;
+ border-color: #188038;
+ color: #188038;
+ &:before {
+ content: "star";
}
- font-size: 0.875rem;
- ul {
- list-style: none;
- margin: 0;
- padding: 0;
- li {
- margin: 5px 0;
- /* This make it so that a single word gets elided but if there
+ }
+
+ &.todo {
+ background-color: #f1f3f4;
+ border-color: #5f6368;
+ color: #5f6368;
+ &:before {
+ content: "error";
+ }
+ }
+
+ &.warning {
+ background-color: #fce8e6;
+ border-color: #c5221f;
+ color: #c5221f;
+ &:before {
+ content: "warning";
+ }
+ }
+ }
+ }
+ .toc {
+ grid-area: toc;
+ padding: 20px 16px 20px 0;
+
+ position: sticky;
+ top: var(--site-header-height);
+ height: calc(100vh - var(--site-header-height));
+ overflow-y: auto;
+ @include minimal-scrollbar;
+
+ font-family: "Source Sans Pro", sans-serif;
+ word-break: break-word;
+ a {
+ text-decoration: none;
+ }
+ a,
+ a:visited {
+ color: #333;
+ }
+ a.highlighted {
+ font-weight: 500;
+ color: hsl(45, 100%, 40%);
+ }
+ font-size: 0.875rem;
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ li {
+ margin: 5px 0;
+ /* This make it so that a single word gets elided but if there
* are multiple words they span across lines. */
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: break-spaces;
- word-break: normal;
- }
- }
- >ul {
- border-left: 4px solid #ecba2a;
- padding-left: 10px;
- position: static; // Otherwise gets v-centered in the middle.
- top: calc(var(--site-header-height) + 25px);
- }
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: break-spaces;
+ word-break: normal;
+ }
}
+ > ul {
+ border-left: 4px solid #ecba2a;
+ padding-left: 10px;
+ position: static; // Otherwise gets v-centered in the middle.
+ top: calc(var(--site-header-height) + 25px);
+ }
+ }
- @media #{$wide} {
- grid-template-columns: var(--nav-width) auto 0;
- .toc { display: none; }
+ @media #{$wide} {
+ grid-template-columns: var(--nav-width) auto 0;
+ .toc {
+ display: none;
}
- @media #{$mobile} {
+ }
+ @media #{$mobile} {
+ display: block;
+ .doc {
+ margin: 0;
+ padding: 20px;
+ }
+ .nav {
+ // JS will persistently toggle to .after_first_click. This is to
+ // avoid spurious transitions on page load.
+ display: none;
+
+ --nav-width-mobile: calc(min(90vw, 360px));
+ width: var(--nav-width-mobile);
+ position: fixed;
+ z-index: 2;
+ height: 100vh;
+ overflow-y: auto;
+ top: var(--site-header-height);
+ transition: transform var(--anim-ease) var(--anim-time),
+ box-shadow var(--anim-ease) var(--anim-time),
+ visibility ease var(--anim-time);
+ transform: translateX(calc(-1 * var(--nav-width-mobile)));
+ visibility: hidden;
+ > ul {
+ position: static;
+ top: 0;
+ }
+ &.after_first_click {
display: block;
- .doc {
- margin: 0;
- padding: 20px;
- }
- .nav {
- // JS will persistently toggle to .after_first_click. This is to
- // avoid spurious transitions on page load.
- display: none;
-
- --nav-width-mobile: calc(min(90vw, 360px));
- width: var(--nav-width-mobile);
- position: fixed;
- z-index: 2;
- height: 100vh;
- overflow-y: auto;
- top: var(--site-header-height);
- transition: transform var(--anim-ease) var(--anim-time),
- box-shadow var(--anim-ease) var(--anim-time),
- visibility ease var(--anim-time);
- transform: translateX(calc(-1 * var(--nav-width-mobile)));
- visibility: hidden;
- >ul {
- position: static;
- top: 0;
- }
- &.after_first_click {
- display: block;
- }
- &.expanded {
- visibility: visible;
- transform: translateX(0);
- box-shadow: 0 1px 0 100vw rgba(0,0,0,0.4);
- }
- }
+ }
+ &.expanded {
+ visibility: visible;
+ transform: translateX(0);
+ box-shadow: 0 1px 0 100vw rgba(0, 0, 0, 0.4);
+ }
}
+ }
}
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index 0ca6c01..0c18970 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -35,6 +35,7 @@
import {TrackMouseEvent, TrackRenderContext} from '../public/track';
import {Point2D, VerticalBounds} from '../base/geom';
import {Trace} from '../public/trace';
+import {SourceDataset, Dataset} from '../trace_processor/dataset';
// The common class that underpins all tracks drawing slices.
@@ -972,6 +973,17 @@
});
return {ts: Time.fromRaw(row.ts), dur: Duration.fromRaw(row.dur)};
}
+
+ getDataset(): Dataset | undefined {
+ return new SourceDataset({
+ src: this.getSqlSource(),
+ schema: {
+ id: NUM,
+ ts: LONG,
+ dur: LONG,
+ },
+ });
+ }
}
// This is the argument passed to onSliceOver(args).
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index ed9b5f0..7a23285 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -16,7 +16,7 @@
import {TrackEventDetailsPanel} from '../public/details_panel';
import {TrackEventSelection} from '../public/selection';
import {Slice} from '../public/track';
-import {STR_NULL} from '../trace_processor/query_result';
+import {LONG, NUM, STR, STR_NULL} from '../trace_processor/query_result';
import {
BASE_ROW,
BaseSliceTrack,
@@ -30,6 +30,7 @@
import {renderDuration} from './widgets/duration';
import {TraceImpl} from '../core/trace_impl';
import {assertIsInstance} from '../base/logging';
+import {SourceDataset, Dataset} from '../trace_processor/dataset';
export const NAMED_ROW = {
// Base columns (tsq, ts, dur, id, depth).
@@ -80,4 +81,16 @@
// because this class is exposed to plugins (which see only Trace).
return new ThreadSliceDetailsPanel(assertIsInstance(this.trace, TraceImpl));
}
+
+ override getDataset(): Dataset | undefined {
+ return new SourceDataset({
+ src: this.getSqlSource(),
+ schema: {
+ id: NUM,
+ name: STR,
+ ts: LONG,
+ dur: LONG,
+ },
+ });
+ }
}
diff --git a/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts
index 1bb31a5..0486e6e 100644
--- a/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts
+++ b/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts
@@ -19,7 +19,14 @@
import {NewTrackArgs} from '../../frontend/track';
import {TrackEventDetails} from '../../public/selection';
import {Slice} from '../../public/track';
-import {LONG_NULL} from '../../trace_processor/query_result';
+import {SourceDataset, Dataset} from '../../trace_processor/dataset';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ NUM_NULL,
+ STR,
+} from '../../trace_processor/query_result';
export const THREAD_SLICE_ROW = {
// Base columns (tsq, ts, dur, id, depth).
@@ -104,4 +111,21 @@
tableName: 'slice',
};
}
+
+ override getDataset(): Dataset {
+ return new SourceDataset({
+ src: `slice`,
+ filter: {
+ col: 'track_id',
+ in: this.trackIds,
+ },
+ schema: {
+ id: NUM,
+ name: STR,
+ ts: LONG,
+ dur: LONG,
+ parent_id: NUM_NULL,
+ },
+ });
+ }
}
diff --git a/ui/src/plugins/dev.perfetto.AsyncSlices/slice_selection_aggregator.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/slice_selection_aggregator.ts
index 55f7c95..23226bc 100644
--- a/ui/src/plugins/dev.perfetto.AsyncSlices/slice_selection_aggregator.ts
+++ b/ui/src/plugins/dev.perfetto.AsyncSlices/slice_selection_aggregator.ts
@@ -16,16 +16,27 @@
import {AreaSelection} from '../../public/selection';
import {Engine} from '../../trace_processor/engine';
import {AreaSelectionAggregator} from '../../public/selection';
-import {SLICE_TRACK_KIND} from '../../public/track_kinds';
+import {UnionDataset} from '../../trace_processor/dataset';
+import {LONG, NUM, STR} from '../../trace_processor/query_result';
export class SliceSelectionAggregator implements AreaSelectionAggregator {
readonly id = 'slice_aggregation';
async createAggregateView(engine: Engine, area: AreaSelection) {
- const selectedTrackKeys = getSelectedTrackSqlIds(area);
-
- if (selectedTrackKeys.length === 0) return false;
-
+ const desiredSchema = {
+ id: NUM,
+ name: STR,
+ ts: LONG,
+ dur: LONG,
+ };
+ const validDatasets = area.tracks
+ .map((track) => track.track.getDataset?.())
+ .filter((ds) => ds !== undefined)
+ .filter((ds) => ds.implements(desiredSchema));
+ if (validDatasets.length === 0) {
+ return false;
+ }
+ const unionDataset = new UnionDataset(validDatasets);
await engine.query(`
create or replace perfetto table ${this.id} as
select
@@ -33,12 +44,13 @@
sum(dur) AS total_dur,
sum(dur)/count() as avg_dur,
count() as occurrences
- from slices
- where track_id in (${selectedTrackKeys})
- and ts + dur > ${area.start}
+ from (${unionDataset.optimize().query()})
+ where
+ ts + dur > ${area.start}
and ts < ${area.end}
group by name
`);
+
return true;
}
@@ -83,14 +95,3 @@
];
}
}
-
-function getSelectedTrackSqlIds(area: AreaSelection): number[] {
- const selectedTrackKeys: number[] = [];
- for (const trackInfo of area.tracks) {
- if (trackInfo?.tags?.kind === SLICE_TRACK_KIND) {
- trackInfo.tags.trackIds &&
- selectedTrackKeys.push(...trackInfo.tags.trackIds);
- }
- }
- return selectedTrackKeys;
-}
diff --git a/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts b/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
index d75dd77..2c19110 100644
--- a/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
@@ -102,6 +102,15 @@
tableName: 'slice',
};
}
+
+ // Override dataset from base class NamedSliceTrack as we don't want these
+ // tracks to participate in generic area selection aggregation (frames tracks
+ // have their own dedicated aggregation panel).
+ // TODO(stevegolton): In future CLs this will be handled with aggregation keys
+ // instead, as this track will have to expose a dataset anyway.
+ override getDataset() {
+ return undefined;
+ }
}
function getColorSchemeForJank(
diff --git a/ui/src/plugins/dev.perfetto.Ftrace/ftrace_track.ts b/ui/src/plugins/dev.perfetto.Ftrace/ftrace_track.ts
index 78c59c8..4ef7793 100644
--- a/ui/src/plugins/dev.perfetto.Ftrace/ftrace_track.ts
+++ b/ui/src/plugins/dev.perfetto.Ftrace/ftrace_track.ts
@@ -20,10 +20,11 @@
import {TrackData} from '../../common/track_data';
import {Engine} from '../../trace_processor/engine';
import {Track} from '../../public/track';
-import {LONG, STR} from '../../trace_processor/query_result';
+import {LONG, NUM, STR} from '../../trace_processor/query_result';
import {FtraceFilter} from './common';
import {Monitor} from '../../base/monitor';
import {TrackRenderContext} from '../../public/track';
+import {SourceDataset, Dataset} from '../../trace_processor/dataset';
const MARGIN = 2;
const RECT_HEIGHT = 18;
@@ -56,6 +57,25 @@
this.monitor = new Monitor([() => store.state]);
}
+ getDataset(): Dataset {
+ return new SourceDataset({
+ // 'ftrace_event' doesn't have a dur column, but injecting dur=0 (all
+ // ftrace events are effectively 'instant') allows us to participate in
+ // generic slice aggregations
+ src: 'select id, ts, 0 as dur, name from ftrace_event',
+ schema: {
+ id: NUM,
+ name: STR,
+ ts: LONG,
+ dur: LONG,
+ },
+ filter: {
+ col: 'cpu',
+ eq: this.cpu,
+ },
+ });
+ }
+
async onUpdate({
visibleWindow,
resolution,
diff --git a/ui/src/public/track.ts b/ui/src/public/track.ts
index 94ac9e7..6d1b1dc 100644
--- a/ui/src/public/track.ts
+++ b/ui/src/public/track.ts
@@ -20,6 +20,7 @@
import {ColorScheme} from './color_scheme';
import {TrackEventDetailsPanel} from './details_panel';
import {TrackEventDetails, TrackEventSelection} from './selection';
+import {Dataset} from '../trace_processor/dataset';
export interface TrackManager {
/**
@@ -175,6 +176,12 @@
onMouseOut?(): void;
/**
+ * Optional: Returns a dataset that represents the events displayed on this
+ * track.
+ */
+ getDataset?(): Dataset | undefined;
+
+ /**
* Optional: Get details of a track event given by eventId on this track.
*/
getSelectionDetails?(eventId: number): Promise<TrackEventDetails | undefined>;
diff --git a/ui/src/trace_processor/dataset.ts b/ui/src/trace_processor/dataset.ts
new file mode 100644
index 0000000..25c64cb
--- /dev/null
+++ b/ui/src/trace_processor/dataset.ts
@@ -0,0 +1,290 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {assertUnreachable} from '../base/logging';
+import {getOrCreate} from '../base/utils';
+import {ColumnType, SqlValue} from './query_result';
+
+/**
+ * A dataset defines a set of rows in TraceProcessor and a schema of the
+ * resultant columns. Dataset implementations describe how to get the data in
+ * different ways - e.g. 'source' datasets define a dataset as a table name (or
+ * select statement) + filters, whereas a 'union' dataset defines a dataset as
+ * the union of other datasets.
+ *
+ * The idea is that users can build arbitrarily complex trees of datasets, then
+ * at any point call `optimize()` to create the smallest possible tree that
+ * represents the same dataset, and `query()` which produces a select statement
+ * for the resultant dataset.
+ *
+ * Users can also use the `schema` property and `implements()` to get and test
+ * the schema of a given dataset.
+ */
+export interface Dataset {
+ /**
+ * Get or calculate the resultant schema of this dataset.
+ */
+ readonly schema: DatasetSchema;
+
+ /**
+ * Produce a query for this dataset.
+ *
+ * @param schema - The schema to use for extracting columns - if undefined,
+ * the most specific possible schema is evaluated from the dataset first and
+ * used instead.
+ */
+ query(schema?: DatasetSchema): string;
+
+ /**
+ * Optimizes a dataset into the smallest possible expression.
+ *
+ * For example by combining elements of union data sets that have the same src
+ * and similar filters into a single set.
+ *
+ * For example, the following 'union' dataset...
+ *
+ * ```
+ * {
+ * union: [
+ * {
+ * src: 'foo',
+ * schema: {
+ * 'a': NUM,
+ * 'b': NUM,
+ * },
+ * filter: {col: 'a', eq: 1},
+ * },
+ * {
+ * src: 'foo',
+ * schema: {
+ * 'a': NUM,
+ * 'b': NUM,
+ * },
+ * filter: {col: 'a', eq: 2},
+ * },
+ * ]
+ * }
+ * ```
+ *
+ * ...will be combined into a single 'source' dataset...
+ *
+ * ```
+ * {
+ * src: 'foo',
+ * schema: {
+ * 'a': NUM,
+ * 'b': NUM,
+ * },
+ * filter: {col: 'a', in: [1, 2]},
+ * },
+ * ```
+ */
+ optimize(): Dataset;
+
+ /**
+ * Returns true if this dataset implements a given schema.
+ *
+ * @param schema - The schema to test against.
+ */
+ implements(schema: DatasetSchema): boolean;
+}
+
+/**
+ * Defines a list of columns and types that define the shape of the data
+ * represented by a dataset.
+ */
+export type DatasetSchema = Record<string, ColumnType>;
+
+/**
+ * A filter used to express that a column must equal a value.
+ */
+interface EqFilter {
+ readonly col: string;
+ readonly eq: SqlValue;
+}
+
+/**
+ * A filter used to express that column must be one of a set of values.
+ */
+interface InFilter {
+ readonly col: string;
+ readonly in: ReadonlyArray<SqlValue>;
+}
+
+/**
+ * Union of all filter types.
+ */
+type Filter = EqFilter | InFilter;
+
+/**
+ * Named arguments for a SourceDataset.
+ */
+interface SourceDatasetConfig {
+ readonly src: string;
+ readonly schema: DatasetSchema;
+ readonly filter?: Filter;
+}
+
+/**
+ * Defines a dataset with a source SQL select statement of table name, a
+ * schema describing the columns, and an optional filter.
+ */
+export class SourceDataset implements Dataset {
+ readonly src: string;
+ readonly schema: DatasetSchema;
+ readonly filter?: Filter;
+
+ constructor(config: SourceDatasetConfig) {
+ this.src = config.src;
+ this.schema = config.schema;
+ this.filter = config.filter;
+ }
+
+ query(schema?: DatasetSchema) {
+ schema = schema ?? this.schema;
+ const cols = Object.keys(schema);
+ const whereClause = this.filterToQuery();
+ return `select ${cols.join(', ')} from (${this.src}) ${whereClause}`.trim();
+ }
+
+ optimize() {
+ // Cannot optimize SourceDataset
+ return this;
+ }
+
+ implements(schema: DatasetSchema) {
+ return Object.entries(schema).every(([name, kind]) => {
+ return name in this.schema && this.schema[name] === kind;
+ });
+ }
+
+ private filterToQuery() {
+ const filter = this.filter;
+ if (filter === undefined) {
+ return '';
+ }
+ if ('eq' in filter) {
+ return `where ${filter.col} = ${filter.eq}`;
+ } else if ('in' in filter) {
+ return `where ${filter.col} in (${filter.in.join(',')})`;
+ } else {
+ assertUnreachable(filter);
+ }
+ }
+}
+
+/**
+ * A dataset that represents the union of multiple datasets.
+ */
+export class UnionDataset implements Dataset {
+ constructor(readonly union: ReadonlyArray<Dataset>) {}
+
+ get schema(): DatasetSchema {
+ // Find the minimal set of columns that are supported by all datasets of
+ // the union
+ let sch: Record<string, ColumnType> | undefined = undefined;
+ this.union.forEach((ds) => {
+ const dsSchema = ds.schema;
+ if (sch === undefined) {
+ // First time just use this one
+ sch = dsSchema;
+ } else {
+ const newSch: Record<string, ColumnType> = {};
+ for (const [key, kind] of Object.entries(sch)) {
+ if (key in dsSchema && dsSchema[key] === kind) {
+ newSch[key] = kind;
+ }
+ }
+ sch = newSch;
+ }
+ });
+ return sch ?? {};
+ }
+
+ query(schema?: DatasetSchema): string {
+ schema = schema ?? this.schema;
+ return this.union
+ .map((dataset) => dataset.query(schema))
+ .join(' union all ');
+ }
+
+ optimize(): Dataset {
+ // Recursively optimize each dataset of this union
+ const optimizedUnion = this.union.map((ds) => ds.optimize());
+
+ // Find all source datasets and combine then based on src
+ const combinedSrcSets = new Map<string, SourceDataset[]>();
+ const otherDatasets: Dataset[] = [];
+ for (const e of optimizedUnion) {
+ if (e instanceof SourceDataset) {
+ const set = getOrCreate(combinedSrcSets, e.src, () => []);
+ set.push(e);
+ } else {
+ otherDatasets.push(e);
+ }
+ }
+
+ const mergedSrcSets = Array.from(combinedSrcSets.values()).map(
+ (srcGroup) => {
+ if (srcGroup.length === 1) return srcGroup[0];
+
+ // Combine schema across all members in the union
+ const combinedSchema = srcGroup.reduce((acc, e) => {
+ Object.assign(acc, e.schema);
+ return acc;
+ }, {} as DatasetSchema);
+
+ // Merge filters for the same src
+ const inFilters: InFilter[] = [];
+ for (const {filter} of srcGroup) {
+ if (filter) {
+ if ('eq' in filter) {
+ inFilters.push({col: filter.col, in: [filter.eq]});
+ } else {
+ inFilters.push(filter);
+ }
+ }
+ }
+
+ const mergedFilter = mergeFilters(inFilters);
+ return new SourceDataset({
+ src: srcGroup[0].src,
+ schema: combinedSchema,
+ filter: mergedFilter,
+ });
+ },
+ );
+
+ const finalUnion = [...mergedSrcSets, ...otherDatasets];
+
+ if (finalUnion.length === 1) {
+ return finalUnion[0];
+ } else {
+ return new UnionDataset(finalUnion);
+ }
+ }
+
+ implements(schema: DatasetSchema) {
+ return Object.entries(schema).every(([name, kind]) => {
+ return name in this.schema && this.schema[name] === kind;
+ });
+ }
+}
+
+function mergeFilters(filters: InFilter[]): InFilter | undefined {
+ if (filters.length === 0) return undefined;
+ const col = filters[0].col;
+ const values = new Set(filters.flatMap((filter) => filter.in));
+ return {col, in: Array.from(values)};
+}
diff --git a/ui/src/trace_processor/dataset_unittest.ts b/ui/src/trace_processor/dataset_unittest.ts
new file mode 100644
index 0000000..2bd4e53
--- /dev/null
+++ b/ui/src/trace_processor/dataset_unittest.ts
@@ -0,0 +1,228 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {SourceDataset, UnionDataset} from './dataset';
+import {LONG, NUM, STR} from './query_result';
+
+test('get query for simple dataset', () => {
+ const dataset = new SourceDataset({
+ src: 'slice',
+ schema: {id: NUM},
+ });
+
+ expect(dataset.query()).toEqual('select id from (slice)');
+});
+
+test("get query for simple dataset with 'eq' filter", () => {
+ const dataset = new SourceDataset({
+ src: 'slice',
+ schema: {id: NUM},
+ filter: {
+ col: 'id',
+ eq: 123,
+ },
+ });
+
+ expect(dataset.query()).toEqual('select id from (slice) where id = 123');
+});
+
+test("get query for simple dataset with an 'in' filter", () => {
+ const dataset = new SourceDataset({
+ src: 'slice',
+ schema: {id: NUM},
+ filter: {
+ col: 'id',
+ in: [123, 456],
+ },
+ });
+
+ expect(dataset.query()).toEqual(
+ 'select id from (slice) where id in (123,456)',
+ );
+});
+
+test('get query for union dataset', () => {
+ const dataset = new UnionDataset([
+ new SourceDataset({
+ src: 'slice',
+ schema: {id: NUM},
+ filter: {
+ col: 'id',
+ eq: 123,
+ },
+ }),
+ new SourceDataset({
+ src: 'slice',
+ schema: {id: NUM},
+ filter: {
+ col: 'id',
+ eq: 456,
+ },
+ }),
+ ]);
+
+ expect(dataset.query()).toEqual(
+ 'select id from (slice) where id = 123 union all select id from (slice) where id = 456',
+ );
+});
+
+test('doesImplement', () => {
+ const dataset = new SourceDataset({
+ src: 'slice',
+ schema: {id: NUM, ts: LONG},
+ });
+
+ expect(dataset.implements({id: NUM})).toBe(true);
+ expect(dataset.implements({id: NUM, ts: LONG})).toBe(true);
+ expect(dataset.implements({id: NUM, ts: LONG, name: STR})).toBe(false);
+ expect(dataset.implements({id: LONG})).toBe(false);
+});
+
+test('find the schema of a simple dataset', () => {
+ const dataset = new SourceDataset({
+ src: 'slice',
+ schema: {id: NUM, ts: LONG},
+ });
+
+ expect(dataset.schema).toMatchObject({id: NUM, ts: LONG});
+});
+
+test('find the schema of a union where source sets differ in their names', () => {
+ const dataset = new UnionDataset([
+ new SourceDataset({
+ src: 'slice',
+ schema: {foo: NUM},
+ }),
+ new SourceDataset({
+ src: 'slice',
+ schema: {bar: NUM},
+ }),
+ ]);
+
+ expect(dataset.schema).toMatchObject({});
+});
+
+test('find the schema of a union with differing source sets', () => {
+ const dataset = new UnionDataset([
+ new SourceDataset({
+ src: 'slice',
+ schema: {foo: NUM},
+ }),
+ new SourceDataset({
+ src: 'slice',
+ schema: {foo: LONG},
+ }),
+ ]);
+
+ expect(dataset.schema).toMatchObject({});
+});
+
+test('find the schema of a union with one column in common', () => {
+ const dataset = new UnionDataset([
+ new SourceDataset({
+ src: 'slice',
+ schema: {foo: NUM, bar: NUM},
+ }),
+ new SourceDataset({
+ src: 'slice',
+ schema: {foo: NUM, baz: NUM},
+ }),
+ ]);
+
+ expect(dataset.schema).toMatchObject({foo: NUM});
+});
+
+test('optimize a union dataset', () => {
+ const dataset = new UnionDataset([
+ new SourceDataset({
+ src: 'slice',
+ schema: {},
+ filter: {
+ col: 'track_id',
+ eq: 123,
+ },
+ }),
+ new SourceDataset({
+ src: 'slice',
+ schema: {},
+ filter: {
+ col: 'track_id',
+ eq: 456,
+ },
+ }),
+ ]);
+
+ expect(dataset.optimize()).toEqual({
+ src: 'slice',
+ schema: {},
+ filter: {
+ col: 'track_id',
+ in: [123, 456],
+ },
+ });
+});
+
+test('optimize a union dataset with different types of filters', () => {
+ const dataset = new UnionDataset([
+ new SourceDataset({
+ src: 'slice',
+ schema: {},
+ filter: {
+ col: 'track_id',
+ eq: 123,
+ },
+ }),
+ new SourceDataset({
+ src: 'slice',
+ schema: {},
+ filter: {
+ col: 'track_id',
+ in: [456, 789],
+ },
+ }),
+ ]);
+
+ expect(dataset.optimize()).toEqual({
+ src: 'slice',
+ schema: {},
+ filter: {
+ col: 'track_id',
+ in: [123, 456, 789],
+ },
+ });
+});
+
+test('optimize a union dataset with different schemas', () => {
+ const dataset = new UnionDataset([
+ new SourceDataset({
+ src: 'slice',
+ schema: {foo: NUM},
+ }),
+ new SourceDataset({
+ src: 'slice',
+ schema: {bar: NUM},
+ }),
+ ]);
+
+ expect(dataset.optimize()).toEqual({
+ src: 'slice',
+ // The resultant schema is the combination of the union's member's schemas,
+ // as we know the source is the same as we know we can get all of the 'seen'
+ // columns from the source.
+ schema: {
+ foo: NUM,
+ bar: NUM,
+ },
+ });
+});