infra: add perfetto.dev site
Merge the sources of the new website @ perfetto.dev
Change-Id: Ie3bc59fa3f42a41360bf07118d2ad8e12f409660
diff --git a/infra/perfetto.dev/src/assets/README.md b/infra/perfetto.dev/src/assets/README.md
new file mode 100644
index 0000000..9f6190a
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/README.md
@@ -0,0 +1,2 @@
+The vector drawings (.sketch) are stored in the internal teams drive
+under Perfetto -> Graphics -> "Perfetto Site.sketch"
diff --git a/infra/perfetto.dev/src/assets/analysis.png b/infra/perfetto.dev/src/assets/analysis.png
new file mode 100644
index 0000000..f2ccdd6
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/analysis.png
Binary files differ
diff --git a/infra/perfetto.dev/src/assets/app_tracing.png b/infra/perfetto.dev/src/assets/app_tracing.png
new file mode 100644
index 0000000..18fdc10
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/app_tracing.png
Binary files differ
diff --git a/infra/perfetto.dev/src/assets/brand.png b/infra/perfetto.dev/src/assets/brand.png
new file mode 100644
index 0000000..29afdc4
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/brand.png
Binary files differ
diff --git a/infra/perfetto.dev/src/assets/favicon.png b/infra/perfetto.dev/src/assets/favicon.png
new file mode 100644
index 0000000..c405e24
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/favicon.png
Binary files differ
diff --git a/infra/perfetto.dev/src/assets/home.png b/infra/perfetto.dev/src/assets/home.png
new file mode 100644
index 0000000..935e2c3
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/home.png
Binary files differ
diff --git a/infra/perfetto.dev/src/assets/script.js b/infra/perfetto.dev/src/assets/script.js
new file mode 100644
index 0000000..91c57e5
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/script.js
@@ -0,0 +1,287 @@
+// Copyright (C) 2020 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.
+
+'use strict';
+
+let tocAnchors = [];
+let lastMouseOffY = 0;
+let onloadFired = false;
+const postLoadActions = [];
+let tocEventHandlersInstalled = false;
+let resizeObserver = undefined;
+
+// Handles redirects from the old docs.perfetto.dev.
+const legacyRedirectMap = {
+ '#/contributing': '/docs/contributing/getting-started#community',
+ '#/build-instructions': '/docs/contributing/build-instructions',
+ '#/testing': '/docs/contributing/testing',
+ '#/app-instrumentation': '/docs/instrumentation/tracing-sdk',
+ '#/recording-traces': '/docs/instrumentation/tracing-sdk#recording',
+ '#/running': '/docs/quickstart/android-tracing',
+ '#/long-traces': '/docs/concepts/config#long-traces',
+ '#/detached-mode': '/docs/concepts/detached-mode',
+ '#/heapprofd': '/docs/data-sources/native-heap-profiler',
+ '#/java-hprof': '/docs/data-sources/java-heap-profiler',
+ '#/trace-processor': '/docs/analysis/trace-processor',
+ '#/analysis': '/docs/analysis/trace-processor#annotations',
+ '#/metrics': '/docs/analysis/metrics',
+ '#/traceconv': '/docs/quickstart/traceconv',
+ '#/clock-sync': '/docs/concepts/clock-sync',
+ '#/architecture': '/docs/concepts/service-model',
+};
+
+function doAfterLoadEvent(action) {
+ if (onloadFired) {
+ return action();
+ }
+ postLoadActions.push(action);
+}
+
+function setupSandwichMenu() {
+ const header = document.querySelector('.site-header');
+ const docsNav = document.querySelector('.nav');
+ const menu = header.querySelector('.menu');
+ menu.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ // If we are displaying any /docs, toggle the navbar instead (the TOC).
+ if (docsNav) {
+ // |after_first_click| is to avoid spurious transitions on page load.
+ docsNav.classList.add('after_first_click');
+ updateNav();
+ setTimeout(() => docsNav.classList.toggle('expanded'), 0);
+ } else {
+ header.classList.toggle('expanded');
+ }
+ });
+}
+
+// (Re-)Generates the Table Of Contents for docs (the right-hand-side one).
+function updateTOC() {
+ const tocContainer = document.querySelector('.docs .toc');
+ if (!tocContainer)
+ return;
+ const toc = document.createElement('ul');
+ const anchors = document.querySelectorAll('.doc a.anchor');
+ tocAnchors = [];
+ for (const anchor of anchors) {
+ const li = document.createElement('li');
+ const link = document.createElement('a');
+ link.innerText = anchor.parentElement.innerText;
+ link.href = anchor.href;
+ link.onclick = () => {
+ onScroll(link)
+ };
+ li.appendChild(link);
+ if (anchor.parentElement.tagName === 'H3')
+ li.style.paddingLeft = '10px';
+ toc.appendChild(li);
+ doAfterLoadEvent(() => {
+ tocAnchors.push(
+ {top: anchor.offsetTop + anchor.offsetHeight / 2, obj: link});
+ });
+ }
+ tocContainer.innerHTML = '';
+ tocContainer.appendChild(toc);
+
+ // Add event handlers on the first call (can be called more than once to
+ // recompute anchors on resize).
+ if (tocEventHandlersInstalled)
+ return;
+ tocEventHandlersInstalled = true;
+ const doc = document.querySelector('.doc');
+ const passive = {passive: true};
+ if (doc) {
+ const offY = doc.offsetTop;
+ doc.addEventListener('mousemove', (e) => onMouseMove(offY, e), passive);
+ doc.addEventListener('mouseleave', () => {
+ lastMouseOffY = 0;
+ }, passive);
+ }
+ window.addEventListener('scroll', () => onScroll(), passive);
+ resizeObserver = new ResizeObserver(() => requestAnimationFrame(() => {
+ updateNav();
+ updateTOC();
+ }));
+ resizeObserver.observe(doc);
+}
+
+// Highlights the current TOC anchor depending on the scroll offset.
+function onMouseMove(offY, e) {
+ lastMouseOffY = e.clientY - offY;
+ onScroll();
+}
+
+function onScroll(forceHighlight) {
+ const y = document.documentElement.scrollTop + lastMouseOffY;
+ let highEl = undefined;
+ for (const x of tocAnchors) {
+ if (y < x.top)
+ continue;
+ highEl = x.obj;
+ }
+ for (const link of document.querySelectorAll('.docs .toc a')) {
+ if ((!forceHighlight && link === highEl) || (forceHighlight === link)) {
+ link.classList.add('highlighted');
+ } else {
+ link.classList.remove('highlighted');
+ }
+ }
+}
+
+// This function needs to be idempotent as it is called more than once (on every
+// resize).
+function updateNav() {
+ const curDoc = document.querySelector('.doc');
+ let curFileName = '';
+ if (curDoc)
+ curFileName = curDoc.dataset['mdFile'];
+
+ // First identify all the top-level nav entries (Quickstart, Data Sources,
+ // ...) and make them compressible.
+ const toplevelSections = document.querySelectorAll('.docs .nav > ul > li');
+ const toplevelLinks = [];
+ for (const sec of toplevelSections) {
+ const childMenu = sec.querySelector('ul');
+ if (!childMenu) {
+ // Don't make it compressible if it has no children (e.g. the very
+ // first 'Introduction' link).
+ continue;
+ }
+
+ // Don't make it compressible if the entry has an actual link (e.g. the very
+ // first 'Introduction' link), because otherwise it become ambiguous whether
+ // the link should toggle or open the link.
+ const link = sec.querySelector('a');
+ if (!link || !link.href.endsWith('#'))
+ continue;
+
+ sec.classList.add('compressible');
+
+ // Remember the compressed status as long as the page is opened, so clicking
+ // through links keeps the sidebar in a consistent visual state.
+ const memoKey = `docs.nav.compressed[${link.innerHTML}]`;
+
+ if (sessionStorage.getItem(memoKey) === '1') {
+ sec.classList.add('compressed');
+ }
+ doAfterLoadEvent(() => {
+ childMenu.style.maxHeight = `${childMenu.scrollHeight + 40}px`;
+ });
+
+ toplevelLinks.push(link);
+ link.onclick = (evt) => {
+ evt.preventDefault();
+ sec.classList.toggle('compressed');
+ if (sec.classList.contains('compressed')) {
+ sessionStorage.setItem(memoKey, '1');
+ } else {
+ sessionStorage.removeItem(memoKey);
+ }
+ };
+ }
+
+ const exps = document.querySelectorAll('.docs .nav ul a');
+ let found = false;
+ for (const x of exps) {
+ // If the url of the entry matches the url of the page, mark the item as
+ // highlighted and expand all its parents.
+ if (!x.href)
+ continue;
+ const url = new URL(x.href);
+ if (x.href.endsWith('#')) {
+ // This is a non-leaf link to a menu.
+ if (toplevelLinks.indexOf(x) < 0) {
+ x.removeAttribute('href');
+ }
+ } else if (url.pathname === curFileName && !found) {
+ x.classList.add('selected');
+ doAfterLoadEvent(() => x.scrollIntoViewIfNeeded());
+ found = true; // Highlight only the first occurrence.
+ }
+ }
+}
+
+// If the page contains a ```mermaid ``` block, lazily loads the plugin and
+// renders.
+function initMermaid() {
+ const graphs = document.querySelectorAll('.mermaid');
+
+ // Skip if there are no mermaid graphs to render.
+ if (!graphs.length)
+ return;
+
+ const script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = '/assets/mermaid.min.js';
+ const themeCSS = `
+ .cluster rect { fill: #FCFCFC; stroke: #ddd }
+ .node rect { fill: #DCEDC8; stroke: #8BC34A}
+ .edgeLabel:not(:empty) {
+ border-radius: 6px;
+ font-size: 0.9em;
+ padding: 4px;
+ background: #F5F5F5;
+ border: 1px solid #DDDDDD;
+ color: #666;
+ }
+ `;
+ script.addEventListener('load', () => {
+ mermaid.initialize({
+ startOnLoad: false,
+ themeCSS: themeCSS,
+ securityLevel: 'loose', // To allow #in-page-links
+ });
+ for (const graph of graphs) {
+ requestAnimationFrame(() => {
+ mermaid.init(undefined, graph);
+ graph.classList.add('rendered');
+ });
+ }
+ })
+ document.body.appendChild(script);
+}
+
+window.addEventListener('DOMContentLoaded', () => {
+ updateNav();
+ updateTOC();
+});
+
+window.addEventListener('load', () => {
+ setupSandwichMenu();
+ initMermaid();
+
+ // Don't smooth-scroll on pages that are too long (e.g. reference pages).
+ if (document.body.scrollHeight < 10000) {
+ document.documentElement.style.scrollBehavior = 'smooth';
+ } else {
+ document.documentElement.style.scrollBehavior = 'initial';
+ }
+
+ onloadFired = true;
+ while (postLoadActions.length > 0) {
+ postLoadActions.shift()();
+ }
+
+ updateTOC();
+
+ // Enable animations only after the load event. This is to prevent glitches
+ // when switching pages.
+ document.documentElement.style.setProperty('--anim-enabled', '1')
+});
+
+const fragment = location.hash.split('?')[0].replace('.md', '');
+if (fragment in legacyRedirectMap) {
+ location.replace(legacyRedirectMap[fragment]);
+}
\ No newline at end of file
diff --git a/infra/perfetto.dev/src/assets/sprite.png b/infra/perfetto.dev/src/assets/sprite.png
new file mode 100644
index 0000000..f076c5a
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/sprite.png
Binary files differ
diff --git a/infra/perfetto.dev/src/assets/style.scss b/infra/perfetto.dev/src/assets/style.scss
new file mode 100644
index 0000000..5fc742e
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/style.scss
@@ -0,0 +1,856 @@
+// Copyright (C) 2020 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.
+// -----------------------------------------------------------------------------
+// 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);
+
+ // 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));
+}
+
+$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;
+ }
+}
+
+@media (max-aspect-ratio: 1/1) {
+ :root {
+ --home-highlights-height: 256px;
+ }
+}
+
+* {
+ box-sizing: border-box;
+ -webkit-tap-highlight-color: none;
+}
+
+html {
+ font-family: Roboto, sans-serif;
+ -webkit-font-smoothing: antialiased;
+}
+
+html,
+body {
+ padding: 0;
+ margin: 0;
+}
+
+h1,
+h2,
+h3 {
+ 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.
+ 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) 0px 3px 3px 0px;
+ 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;
+ line-height: var(--site-header-height);
+ }
+
+ @media #{$mobile} {
+ flex-direction: column;
+ >*:not(:first-child) {
+ margin-left: 40px;
+ }
+ .menu {
+ visibility: visible;
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Site header
+// -----------------------------------------------------------------------------
+
+// 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;
+ }
+ }
+ }
+ 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;
+
+ .docs-footer-notice {
+ padding: 0;
+ margin: 0;
+ display: block;
+ }
+
+ 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 {
+ display: block;
+ position: relative;
+ 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));
+ min-height: 25vw;
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: max-content max-content 1fr;
+ 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(max(3.5vw, 2rem), 4rem));
+ font-weight: 400;
+ padding-top: 40px;
+ }
+ h2 {
+ font-size: 1.25rem;
+ font-size: calc(min(max(2vw, 1.25rem), 3rem));
+ font-weight: 200;
+ padding-top: 10px;
+ }
+ .home-img {
+ max-height: 100%;
+ max-width: 100%;
+ margin: auto;
+ padding: 20px 0;
+ }
+ }
+ .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);
+ }
+ }
+ }
+ }
+ .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: 100%;
+ 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;
+ }
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Docs
+// -----------------------------------------------------------------------------
+.docs {
+ 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));
+
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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; }
+ }
+
+ // 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;
+ 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, .1), 0 1px 3px 1px rgba(60, 64, 67, .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;
+ }
+ thead {
+ text-align: left;
+ background-color: #e8eaed;
+ color: #202124;
+ }
+ }
+
+ &[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
+ * column takes most of the width */
+ &: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;
+
+ 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);
+ }
+ }
+
+ @media #{$wide} {
+ grid-template-columns: var(--nav-width) auto 0;
+ .toc { display: none; }
+ }
+ @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;
+ }
+ &.expanded {
+ visibility: visible;
+ transform: translateX(0);
+ box-shadow: 0 1px 0 100vw rgba(0,0,0,0.4);
+ }
+ }
+ }
+}
diff --git a/infra/perfetto.dev/src/assets/sys_profiling.png b/infra/perfetto.dev/src/assets/sys_profiling.png
new file mode 100644
index 0000000..3135e16
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/sys_profiling.png
Binary files differ
diff --git a/infra/perfetto.dev/src/assets/ui.png b/infra/perfetto.dev/src/assets/ui.png
new file mode 100644
index 0000000..331e0ea
--- /dev/null
+++ b/infra/perfetto.dev/src/assets/ui.png
Binary files differ
diff --git a/infra/perfetto.dev/src/gen_proto_reference.js b/infra/perfetto.dev/src/gen_proto_reference.js
new file mode 100644
index 0000000..044929e
--- /dev/null
+++ b/infra/perfetto.dev/src/gen_proto_reference.js
@@ -0,0 +1,147 @@
+// Copyright (C) 2020 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.
+
+// Generation of reference from protos
+
+'use strict';
+
+const protobufjs = require('protobufjs');
+const fs = require('fs');
+const path = require('path');
+const argv = require('yargs').argv
+
+const PROJECT_ROOT =
+ path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
+
+const visited = {};
+
+
+// Removes \n due to 80col wrapping and preserves only end-of-sentence line
+// breaks.
+function singleLineComment(comment) {
+ comment = comment || '';
+ comment = comment.trim();
+ comment = comment.replace(/\.\n/g, '<br>');
+ comment = comment.replace(/\n/g, ' ');
+ return comment;
+}
+
+function getFullName(pType) {
+ let cur = pType;
+ let name = pType.name;
+ while (cur && cur.parent != cur && cur.parent instanceof protobufjs.Type) {
+ name = `${cur.parent.name}.${name}`;
+ cur = cur.parent;
+ }
+ return name;
+}
+
+function genType(pType, depth) {
+ depth = depth || 0;
+ console.assert(pType instanceof protobufjs.ReflectionObject);
+ const fullName = getFullName(pType);
+ if (fullName in visited)
+ return '';
+ visited[fullName] = true;
+
+ const heading = '#' +
+ '#'.repeat(Math.min(depth, 2));
+ const anchor = depth > 0 ? `{#${fullName}} ` : '';
+ let md = `${heading} ${anchor}${fullName}`;
+ md += '\n';
+ const fileName = path.basename(pType.filename);
+ const relPath = path.relative(PROJECT_ROOT, pType.filename);
+ md += `${(pType.comment || '').replace(/(\n)?^\s*next.*\bid:.*$/img, '')}`;
+ md += `\n\nDefined in [${fileName}](/${relPath})\n\n`;
+
+ const subTypes = [];
+
+ if (pType instanceof protobufjs.Enum) {
+ md += '#### Enum values:\n';
+ md += 'Name | Value | Description\n';
+ md += '---- | ----- | -----------\n';
+ for (const enumName of Object.keys(pType.values)) {
+ const enumVal = pType.values[enumName];
+ const comment = singleLineComment(pType.comments[enumName]);
+ md += `${enumName} | ${enumVal} | ${comment}\n`
+ }
+ } else {
+ md += '#### Fields:\n';
+ md += 'Field | Type | Description\n';
+ md += '----- | ---- | -----------\n';
+
+ for (const fieldName in pType.fields) {
+ const field = pType.fields[fieldName];
+ let type = field.type;
+ if (field.repeated) {
+ type = `${type}[]`;
+ }
+ if (field.resolvedType) {
+ // The TraceConfig proto is linked from the TracePacket reference.
+ // Instead of recursing and generating the TraceConfig types all over
+ // again, just link to the dedicated TraceConfig reference page.
+ if (getFullName(field.resolvedType) === 'TraceConfig') {
+ type = `[${type}](/docs/reference/trace-config-proto.autogen)`;
+ } else {
+ subTypes.push(field.resolvedType);
+ type = `[${type}](#${getFullName(field.resolvedType)})`;
+ }
+ }
+ md += `${fieldName} | ${type} | ${singleLineComment(field.comment)}\n`
+ }
+ }
+ md += '\n\n\n\n';
+
+ for (const subType of subTypes)
+ md += genType(subType, depth + 1);
+
+ return md;
+}
+
+
+function main() {
+ const inProtoFile = argv['i'];
+ const protoName = argv['p'];
+ const outFile = argv['o'];
+ if (!inProtoFile || !protoName) {
+ console.error('Usage: -i input.proto -p protos.RootType [-o out.md]');
+ process.exit(1);
+ }
+
+ const parser = new protobufjs.Root();
+ parser.resolvePath = (_, target) => {
+ if (target == inProtoFile) {
+ // The root proto file passed from the cmdline will be relative to the
+ // root_build_dir (out/xxx) (e.g.: ../../protos/config)
+ return inProtoFile;
+ }
+ // All the other imports, instead, will be relative to the project root
+ // (e.g. protos/config/...)
+ return path.join(PROJECT_ROOT, target);
+ };
+
+ const cfg = parser.loadSync(
+ inProtoFile, {alternateCommentMode: true, keepCase: true});
+ cfg.resolveAll();
+ const traceConfig = cfg.lookup(protoName);
+ const generatedMd = genType(traceConfig);
+ if (outFile) {
+ fs.writeFileSync(outFile, generatedMd);
+ } else {
+ console.log(generatedMd);
+ }
+ process.exit(0);
+}
+
+main();
diff --git a/infra/perfetto.dev/src/gen_sql_tables_reference.js b/infra/perfetto.dev/src/gen_sql_tables_reference.js
new file mode 100644
index 0000000..a605577
--- /dev/null
+++ b/infra/perfetto.dev/src/gen_sql_tables_reference.js
@@ -0,0 +1,305 @@
+// Copyright (C) 2020 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.
+
+// Generation of reference from protos
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const argv = require('yargs').argv
+
+// Removes \n due to 80col wrapping and preserves only end-of-sentence line
+// breaks.
+// TODO dedupe, this is copied from the other gen_proto file.
+function singleLineComment(comment) {
+ comment = comment || '';
+ comment = comment.trim();
+ comment = comment.replace(/\.\n/g, '<br>');
+ comment = comment.replace(/\n/g, ' ');
+ return comment;
+}
+
+// Returns an object describing the table as follows:
+// { name: 'HeapGraphObjectTable',
+// cols: [ {name: 'upid', type: 'uint32_t', optional: false },
+// {name: 'graph_sample_ts', type: 'int64_t', optional: false },
+function parseTableDef(tableDefName, tableDef) {
+ const tableDesc = {
+ name: '', // The SQL table name, e.g. stack_profile_mapping.
+ cppClassName: '', // e.g., StackProfileMappingTable.
+ defMacro: tableDefName, // e.g., PERFETTO_TP_STACK_PROFILE_MAPPING_DEF.
+ comment: '',
+ parent: undefined, // Will be filled afterwards in the resolution phase.
+ parentDefName: '', // e.g., PERFETTO_TP_STACK_PROFILE_MAPPING_DEF.
+ tablegroup: 'Misc', // From @tablegroup in comments.
+ cols: {},
+ };
+ const getOrCreateColumn = (name) => {
+ if (name in tableDesc.cols)
+ return tableDesc.cols[name];
+ tableDesc.cols[name] = {
+ name: name,
+ type: '',
+ comment: '',
+ optional: false,
+ refTableCppName: undefined,
+ joinTable: undefined,
+ joinCol: undefined,
+ };
+ return tableDesc.cols[name];
+ };
+
+ let lastColumn = undefined;
+ for (const line of tableDef.split('\n')) {
+ if (line.startsWith('#define'))
+ continue; // Skip the first line.
+ let m;
+ if (line.startsWith('//')) {
+ let comm = line.replace(/^\s*\/\/\s*/, '');
+ if (m = comm.match(/@tablegroup (.*)/)) {
+ tableDesc.tablegroup = m[1];
+ continue;
+ }
+ if (m = comm.match(/@name (\w+)/)) {
+ tableDesc.name = m[1];
+ continue;
+ }
+ if (m = comm.match(/@param\s+([^ ]+)\s*({\w+})?\s*(.*)/)) {
+ lastColumn = getOrCreateColumn(/*name=*/ m[1]);
+ lastColumn.type = (m[2] || '').replace(/(^{)|(}$)/g, '');
+ lastColumn.comment = m[3];
+ continue;
+ }
+ if (lastColumn === undefined) {
+ tableDesc.comment += `${comm}\n`;
+ } else {
+ lastColumn.comment = `${lastColumn.comment}${comm}\n`;
+ }
+ continue;
+ }
+ if (m = line.match(/^\s*NAME\((\w+)\s*,\s*"(\w+)"/)) {
+ tableDesc.cppClassName = m[1];
+ if (tableDesc.name === '') {
+ tableDesc.name = m[2]; // Set only if not overridden by @name.
+ }
+ continue;
+ }
+ if (m = line.match(/(PERFETTO_TP_ROOT_TABLE|PARENT)\((\w+)/)) {
+ if (m[1] === 'PARENT') {
+ tableDesc.parentDefName = m[2];
+ }
+ continue;
+ }
+ if (m = line.match(/^\s*C\(([^,]+)\s*,\s*(\w+)/)) {
+ const col = getOrCreateColumn(/*name=*/ m[2]);
+ col.type = m[1];
+ if (m = col.type.match(/Optional<(.*)>/)) {
+ col.type = m[1];
+ col.optional = true;
+ }
+ if (col.type === 'StringPool::Id') {
+ col.type = 'string';
+ }
+ const sep = col.type.indexOf('::');
+ if (sep > 0) {
+ col.refTableCppName = col.type.substr(0, sep);
+ }
+ continue;
+ }
+ throw new Error(`Cannot parse line "${line}" from ${tableDefName}`);
+ }
+
+ // Process {@joinable xxx} annotations.
+ const regex = /\s?\{@joinable\s*(\w+)\.(\w+)\s*\}/;
+ for (const col of Object.values(tableDesc.cols)) {
+ const m = col.comment.match(regex)
+ if (m) {
+ col.joinTable = m[1];
+ col.joinCol = m[2];
+ col.comment = col.comment.replace(regex, '');
+ }
+ }
+ return tableDesc;
+}
+
+
+function parseTablesInCppFile(filePath) {
+ const hdr = fs.readFileSync(filePath, 'UTF8');
+ const regex = /^\s*PERFETTO_TP_TABLE\((\w+)\)/mg;
+ let match = regex.exec(hdr);
+ const tables = [];
+ while (match != null) {
+ const tableDefName = match[1];
+ match = regex.exec(hdr);
+
+ // Now let's extract the table definition, that looks like this:
+ // // Some
+ // // Multiline
+ // // Comment
+ // #define PERFETTO_TP_STACK_PROFILE_FRAME_DEF(NAME, PARENT, C) \
+ // NAME(StackProfileFrameTable, "stack_profile_frame") \
+ // PERFETTO_TP_ROOT_TABLE(PARENT, C) \
+ // C(StringPool::Id, name) \
+ // C(StackProfileMappingTable::Id, mapping) \
+ // C(int64_t, rel_pc) \
+ // C(base::Optional<uint32_t>, symbol_set_id)
+ //
+ // Where PERFETTO_TP_STACK_PROFILE_FRAME_DEF is |tableDefName|.
+ let pattern = `(^[ ]*//.*\n)*`;
+ pattern += `^\s*#define\\s+${tableDefName}\\s*\\(`;
+ pattern += `(.*\\\\\\s*\n)+`;
+ pattern += `.+`;
+ const r = new RegExp(pattern, 'mi');
+ const tabMatch = r.exec(hdr);
+ if (!tabMatch) {
+ console.error(`could not find table ${tableDefName}`);
+ continue;
+ }
+ tables.push(parseTableDef(tableDefName, tabMatch[0]));
+ }
+ return tables;
+}
+
+
+function genLink(table) {
+ return `[${table.name}](#${table.name})`;
+}
+
+function tableToMarkdown(table) {
+ let md = `### ${table.name}\n\n`;
+ if (table.parent) {
+ md += `_Extends ${genLink(table.parent)}_\n\n`;
+ }
+ md += table.comment + '\n\n';
+ md += 'Column | Type | Description\n';
+ md += '------ | ---- | -----------\n';
+
+ let curTable = table;
+ while (curTable) {
+ if (curTable != table) {
+ md += `||_Columns inherited from_ ${genLink(curTable)}\n`
+ }
+ for (const col of Object.values(curTable.cols)) {
+ const type = col.type + (col.optional ? '<br>`optional`' : '');
+ let description = col.comment;
+ if (col.joinTable) {
+ description += `\nJoinable with ` +
+ `[${col.joinTable}.${col.joinCol}](#${col.joinTable})`;
+ }
+ md += `${col.name} | ${type} | ${singleLineComment(description)}\n`
+ }
+ curTable = curTable.parent;
+ }
+ md += '\n\n';
+ return md;
+}
+
+function main() {
+ const inFile = argv['i'];
+ const outFile = argv['o'];
+ if (!inFile) {
+ console.error('Usage: -i hdr1.h -i hdr2.h -[-o out.md]');
+ process.exit(1);
+ }
+
+ // Can be either a string (-i single) or an array (-i one -i two).
+ const inFiles = (inFile instanceof Array) ? inFile : [inFile];
+
+ const tables = Array.prototype.concat(...inFiles.map(parseTablesInCppFile));
+
+ // Resolve parents.
+ const tablesIndex = {}; // 'TP_SCHED_SLICE_TABLE_DEF' -> table
+ const tablesByGroup = {}; // 'profilers' => [table1, table2]
+ const tablesCppName = {}; // 'StackProfileMappingTable' => table
+ const tablesByName = {}; // 'profile_mapping' => table
+ for (const table of tables) {
+ tablesIndex[table.defMacro] = table;
+ if (tablesByGroup[table.tablegroup] === undefined) {
+ tablesByGroup[table.tablegroup] = [];
+ }
+ tablesCppName[table.cppClassName] = table;
+ tablesByName[table.name] = table;
+ tablesByGroup[table.tablegroup].push(table);
+ }
+ const tableGroups = Object.keys(tablesByGroup).sort((a, b) => {
+ const keys = {'Tracks': '1', 'Events': '2', 'Misc': 'z'};
+ a = `${keys[a]}_${a}`;
+ b = `${keys[b]}_${b}`;
+ return a.localeCompare(b);
+ });
+
+ for (const table of tables) {
+ if (table.parentDefName) {
+ table.parent = tablesIndex[table.parentDefName];
+ }
+ }
+
+ // Builds a graph of the tables' relationship that can be rendererd with
+ // mermaid.js.
+ let graph = '## Tables diagram\n';
+ const mkLabel = (table) => `${table.defMacro}["${table.name}"]`;
+ for (const tableGroup of tableGroups) {
+ let gaphEdges = '';
+ let gaphLinks = '';
+ graph += `#### ${tableGroup} tables\n`;
+ graph += '```mermaid\ngraph TD\n';
+ graph += ` subgraph ${tableGroup}\n`;
+ for (const table of tablesByGroup[tableGroup]) {
+ graph += ` ${mkLabel(table)}\n`;
+ gaphLinks += ` click ${table.defMacro} "#${table.name}"\n`
+ if (table.parent) {
+ gaphEdges += ` ${mkLabel(table)} --> ${mkLabel(table.parent)}\n`
+ }
+
+ for (const col of Object.values(table.cols)) {
+ let refTable = undefined;
+ if (col.refTableCppName) {
+ refTable = tablesCppName[col.refTableCppName];
+ } else if (col.joinTable) {
+ refTable = tablesByName[col.joinTable];
+ if (!refTable) {
+ throw new Error(`Cannot find @joinable table ${col.joinTable}`);
+ }
+ }
+ if (!refTable)
+ continue;
+ gaphEdges +=
+ ` ${mkLabel(table)} -. ${col.name} .-> ${mkLabel(refTable)}\n`
+ gaphLinks += ` click ${refTable.defMacro} "#${refTable.name}"\n`
+ }
+ }
+ graph += ` end\n`;
+ graph += gaphEdges;
+ graph += gaphLinks;
+ graph += '\n```\n';
+ }
+
+ let md = graph;
+ for (const tableGroup of tableGroups) {
+ md += `## ${tableGroup}\n`
+ for (const table of tablesByGroup[tableGroup]) {
+ md += tableToMarkdown(table);
+ }
+ }
+
+ if (outFile) {
+ fs.writeFileSync(outFile, md);
+ } else {
+ console.log(md);
+ }
+ process.exit(0);
+}
+
+main();
diff --git a/infra/perfetto.dev/src/gen_stats_reference.js b/infra/perfetto.dev/src/gen_stats_reference.js
new file mode 100644
index 0000000..0f7228e
--- /dev/null
+++ b/infra/perfetto.dev/src/gen_stats_reference.js
@@ -0,0 +1,100 @@
+// Copyright (C) 2020 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.
+
+// Generation of reference from protos
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const argv = require('yargs').argv
+
+// Removes \n due to 80col wrapping and preserves only end-of-sentence line
+// breaks.
+// TODO dedupe, this is copied from the other gen_proto file.
+function singleLineComment(comment) {
+ comment = comment || '';
+ comment = comment.trim();
+ comment = comment.replace(/\.\n/g, '<br>');
+ comment = comment.replace(/\n/g, ' ');
+ return comment;
+}
+
+function trimQuotes(s) {
+ if (s === undefined) {
+ return s;
+ }
+ const regex = /\"(.*)"/;
+ let m = regex.exec(s);
+ if (m === null) {
+ return null;
+ }
+ return m[1]
+}
+
+function parseTablesInCppFile(filePath) {
+ const hdr = fs.readFileSync(filePath, 'UTF8');
+ const regex = /^\s*F\(([\s\S]*?)\),\s*\\/mg;
+ let match;
+ let table = [];
+ while ((match = regex.exec(hdr)) !== null) {
+ let def = match[1];
+ let s = def.split(',').map(s => s.trim());
+ table.push({
+ name: s[0],
+ cardinality: s[1],
+ type: s[2],
+ scope: s[3],
+ comment: s[4] === undefined ? undefined :
+ s[4].split('\n').map(trimQuotes).join(' '),
+ });
+ }
+ return table;
+}
+
+
+function tableToMarkdown(table) {
+ let md = `# Trace Processor Stats\n\n`;
+ md += 'Name | Cardinality | Type | Scope | Description |\n';
+ md += '---- | ----------- | ---- | ----- | ----------- |\n';
+ for (const col of table) {
+ md += `${col.name} | ${col.cardinality} | ${col.type} | ${col.scope} | ${
+ singleLineComment(col.comment)} |\n`
+ }
+ md += '\n\n';
+ return md;
+}
+
+function main() {
+ const inFile = argv['i'];
+ const outFile = argv['o'];
+ if (!inFile) {
+ console.error('Usage: -i hdr1.h -i hdr2.h -[-o out.md]');
+ process.exit(1);
+ }
+
+ // Can be either a string (-i single) or an array (-i one -i two).
+ const inFiles = (inFile instanceof Array) ? inFile : [inFile];
+
+ const table = Array.prototype.concat(...inFiles.map(parseTablesInCppFile));
+ const md = tableToMarkdown(table);
+ if (outFile) {
+ fs.writeFileSync(outFile, md);
+ } else {
+ console.log(md);
+ }
+ process.exit(0);
+}
+
+main();
diff --git a/infra/perfetto.dev/src/markdown_render.js b/infra/perfetto.dev/src/markdown_render.js
new file mode 100644
index 0000000..ebb34d8
--- /dev/null
+++ b/infra/perfetto.dev/src/markdown_render.js
@@ -0,0 +1,220 @@
+// Copyright (C) 2020 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.
+
+const ejs = require('ejs');
+const marked = require('marked');
+const argv = require('yargs').argv
+const fs = require('fs-extra');
+const path = require('path');
+const hljs = require('highlight.js');
+
+const CS_BASE_URL =
+ 'https://cs.android.com/android/platform/superproject/+/master:external/perfetto';
+
+const ROOT_DIR = path.dirname(path.dirname(path.dirname(__dirname)));
+const DOCS_DIR = path.join(ROOT_DIR, 'docs');
+
+let outDir = '';
+let curMdFile = '';
+let title = '';
+
+function hrefInDocs(href) {
+ if (href.match(/^(https?:)|^(mailto:)|^#/)) {
+ return undefined;
+ }
+ let pathFromRoot;
+ if (href.startsWith('/')) {
+ pathFromRoot = href;
+ } else {
+ curDocDir = '/' + path.relative(ROOT_DIR, path.dirname(curMdFile));
+ pathFromRoot = path.join(curDocDir, href);
+ }
+ if (pathFromRoot.startsWith('/docs/')) {
+ return pathFromRoot;
+ }
+ return undefined;
+}
+
+function assertNoDeadLink(relPathFromRoot) {
+ relPathFromRoot = relPathFromRoot.replace(/\#.*$/g, ''); // Remove #line.
+
+ // Skip check for build-time generated reference pages.
+ if (relPathFromRoot.endsWith('.autogen'))
+ return;
+
+ const fullPath = path.join(ROOT_DIR, relPathFromRoot);
+ if (!fs.existsSync(fullPath) && !fs.existsSync(fullPath + '.md')) {
+ const msg = `Dead link: ${relPathFromRoot} in ${curMdFile}`;
+ console.error(msg);
+ throw new Error(msg);
+ }
+}
+
+function renderHeading(text, level) {
+ // If the heading has an explicit ${#anchor}, use that. Otherwise infer the
+ // anchor from the text but only for h2 and h3. Note the right-hand-side TOC
+ // is dynamically generated from anchors (explicit or implicit).
+ if (level === 1 && !title) {
+ title = text;
+ }
+ let anchorId = '';
+ const explicitAnchor = /{#([\w-_.]+)}/.exec(text);
+ if (explicitAnchor) {
+ text = text.replace(explicitAnchor[0], '');
+ anchorId = explicitAnchor[1];
+ } else if (level >= 2 && level <= 3) {
+ anchorId = text.toLowerCase().replace(/[^\w]+/g, '-');
+ anchorId = anchorId.replace(/[-]+/g, '-'); // Drop consecutive '-'s.
+ }
+ let anchor = '';
+ if (anchorId) {
+ anchor = `<a name="${anchorId}" class="anchor" href="#${anchorId}"></a>`;
+ }
+ return `<h${level}>${anchor}${text}</h${level}>`;
+}
+
+function renderLink(originalLinkFn, href, title, text) {
+ if (href.startsWith('../')) {
+ throw new Error(
+ `Don\'t use relative paths in docs, always use /docs/xxx ` +
+ `or /src/xxx for both links to docs and code (${href})`)
+ }
+ const docsHref = hrefInDocs(href);
+ let sourceCodeLink = undefined;
+ if (docsHref !== undefined) {
+ // Check that the target doc exists. Skip the check on /reference/ files
+ // that are typically generated at build time.
+ assertNoDeadLink(docsHref);
+ href = docsHref.replace(/[.](md|autogen)\b/, '');
+ href = href.replace(/\/README$/, '/');
+ } else if (href.startsWith('/') && !href.startsWith('//')) {
+ // /tools/xxx -> github/tools/xxx.
+ sourceCodeLink = href;
+ }
+ if (sourceCodeLink !== undefined) {
+ // Fix up line anchors for GitHub link: #42 -> #L42.
+ sourceCodeLink = sourceCodeLink.replace(/#(\d+)$/g, '#L$1')
+ assertNoDeadLink(sourceCodeLink);
+ href = CS_BASE_URL + sourceCodeLink;
+ }
+ return originalLinkFn(href, title, text);
+}
+
+function renderCode(text, lang) {
+ if (lang === 'mermaid') {
+ return `<div class="mermaid">${text}</div>`;
+ }
+
+ let hlHtml = '';
+ if (lang) {
+ hlHtml = hljs.highlight(lang, text).value
+ } else {
+ hlHtml = hljs.highlightAuto(text).value
+ }
+ return `<code class="hljs code-block">${hlHtml}</code>`
+}
+
+function renderImage(originalImgFn, href, title, text) {
+ const docsHref = hrefInDocs(href);
+ if (docsHref !== undefined) {
+ const outFile = outDir + docsHref;
+ const outParDir = path.dirname(outFile);
+ fs.ensureDirSync(outParDir);
+ fs.copyFileSync(ROOT_DIR + docsHref, outFile);
+ }
+ if (href.endsWith('.svg')) {
+ return `<object type="image/svg+xml" data="${href}"></object>`
+ }
+ return originalImgFn(href, title, text);
+}
+
+function renderParagraph(text) {
+ let cssClass = '';
+ if (text.startsWith('NOTE:')) {
+ cssClass = 'note';
+ }
+ else if (text.startsWith('TIP:')) {
+ cssClass = 'tip';
+ }
+ else if (text.startsWith('TODO:') || text.startsWith('FIXME:')) {
+ cssClass = 'todo';
+ }
+ else if (text.startsWith('WARNING:')) {
+ cssClass = 'warning';
+ }
+ else if (text.startsWith('Summary:')) {
+ cssClass = 'summary';
+ }
+ if (cssClass != '') {
+ cssClass = ` class="callout ${cssClass}"`;
+ }
+ return `<p${cssClass}>${text}</p>\n`;
+}
+
+function render(rawMarkdown) {
+ const renderer = new marked.Renderer();
+ const originalLinkFn = renderer.link.bind(renderer);
+ const originalImgFn = renderer.image.bind(renderer);
+ renderer.link = (hr, ti, te) => renderLink(originalLinkFn, hr, ti, te);
+ renderer.image = (hr, ti, te) => renderImage(originalImgFn, hr, ti, te);
+ renderer.code = renderCode;
+ renderer.heading = renderHeading;
+ renderer.paragraph = renderParagraph;
+
+ return marked(rawMarkdown, {renderer: renderer});
+}
+
+function main() {
+ const inFile = argv['i'];
+ const outFile = argv['o'];
+ outDir = argv['odir'];
+ const templateFile = argv['t'];
+ if (!outFile || !outDir) {
+ console.error(
+ 'Usage: --odir site -o out.html [-i input.md] [-t templ.html]');
+ process.exit(1);
+ }
+ curMdFile = inFile;
+
+ let markdownHtml = '';
+ if (inFile) {
+ markdownHtml = render(fs.readFileSync(inFile, 'utf8'));
+ }
+
+ if (templateFile) {
+ // TODO rename nav.html to sitemap or something more mainstream.
+ const navFilePath = path.join(outDir, 'docs', '_nav.html');
+ const fallbackTitle =
+ 'Perfetto - System profiling, app tracing and trace analysis';
+ const templateData = {
+ markdown: markdownHtml,
+ title: title ? `${title} - Perfetto Tracing Docs` : fallbackTitle,
+ fileName: '/' + outFile.split('/').slice(1).join('/'),
+ };
+ if (fs.existsSync(navFilePath)) {
+ templateData['nav'] = fs.readFileSync(navFilePath, 'utf8');
+ }
+ ejs.renderFile(templateFile, templateData, (err, html) => {
+ if (err)
+ throw err;
+ fs.writeFileSync(outFile, html);
+ process.exit(0);
+ });
+ } else {
+ fs.writeFileSync(outFile, markdownHtml);
+ process.exit(0);
+ }
+}
+
+main();
diff --git a/infra/perfetto.dev/src/template_footer.html b/infra/perfetto.dev/src/template_footer.html
new file mode 100644
index 0000000..5649e26
--- /dev/null
+++ b/infra/perfetto.dev/src/template_footer.html
@@ -0,0 +1,14 @@
+
+<footer class="site-footer">
+ <p class="docs-footer-notice">
+ Except as otherwise noted, the content of this page is licensed under the
+ <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons
+ Attribution 4.0 License</a>, and code samples are licensed
+ under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0
+ License</a>. Java is a registered trademark of Oracle and/or its affiliates.
+ </p>
+ <ul>
+ <li><a href="//creativecommons.org/licenses/by/4.0/">Site CC BY 4.0</a></li>
+ <li><a href="//www.google.com/intl/en/policies/privacy/">Privacy</a></li>
+ </ul>
+</footer>
diff --git a/infra/perfetto.dev/src/template_header.html b/infra/perfetto.dev/src/template_header.html
new file mode 100644
index 0000000..45960d9
--- /dev/null
+++ b/infra/perfetto.dev/src/template_header.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title><%= title %></title>
+ <meta property="og:title" content="<%= title %>">
+ <meta property="og:site_name" content="Perfetto">
+ <meta property="og:type" content="website">
+ <meta property="og:locale" content="en">
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <link rel="canonical" href="https://perfetto.dev<%= fileName.replace('/index.html', '/') %>">
+ <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,500,600,800&display=swap" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/icon?family=Roboto+Mono" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet">
+ <link rel="stylesheet" href="/assets/style.css">
+ <link rel="stylesheet" href="/assets/tomorrow-night.css">
+ <link rel="shortcut icon" href="/assets/favicon.png" />
+ <script type="text/javascript" src="/assets/script.js"></script>
+</head>
+<body>
+<header class="site-header">
+ <div class="brand">
+ <a href="/">
+ <img src="/assets/brand.png">
+ <% if (fileName.startsWith('/docs/')) { %>
+ <span class="brand-docs">Docs</span>
+ <% } %>
+ </a>
+ </div>
+ <a href="#toggle" class="menu"><i class="material-icons-round">menu</i></a>
+
+ <a href="/docs/">Docs</a>
+ <a href="/docs/contributing/getting-started#community">Community</a>
+ <a href="https://ui.perfetto.dev/">Trace Viewer</a>
+ <a href="https://github.com/google/perfetto">GitHub</a>
+</header>
diff --git a/infra/perfetto.dev/src/template_index.html b/infra/perfetto.dev/src/template_index.html
new file mode 100644
index 0000000..f580f63
--- /dev/null
+++ b/infra/perfetto.dev/src/template_index.html
@@ -0,0 +1,131 @@
+<%- include('template_header.html'); -%>
+<main class="site-content">
+ <div class="section-wrapper">
+ <section class="banner">
+ <h1><span>System profiling,</span> <span>app tracing</span> <span>and trace analysis</span></h1>
+ <h2>Open-Source · Portable · Efficient</h2>
+ <img src="/assets/home.png" class="home-img" alt="Perfetto">
+ </section>
+ </div>
+
+ <div class="section-wrapper">
+ <section class="home-highlights">
+ <a href="#profiling"><div class="icon"></div>System Profiling</a>
+ <a href="#tracing"><div class="icon"></div>In-App Tracing</a>
+ <a href="#viewer"><div class="icon"></div>Trace Viewer</a>
+ <a href="#analysis"><div class="icon"></div>Trace Analysis</a>
+ </section>
+ </div>
+
+ <div class="section-wrapper">
+ <section class="home-section" id="profiling">
+ <img src="/assets/sys_profiling.png" alt="Profiling illustration">
+ <div>
+ <h2>System-wide profiling for Linux and Android</h2>
+ <div class="home-item">
+ <i class="material-icons-round">sort</i>
+ <h3>Linux kernel tracing</h3>
+ <p>Capture high frequency ftrace data: scheduling activity, task switching latency, CPU frequency and much more</p>
+ </div>
+ <div class="home-item">
+ <i class="material-icons-round">nfc</i>
+ <h3>Userspace profilers and extra probes</h3>
+ <p>Native heap profiling, Java heap profiling, pollers for /proc stat files</p>
+ </div>
+ <div class="home-item">
+ <i class="material-icons-round">android</i>
+ <h3>Built into Android</h3>
+ <p>Part of the platform since Android 9 Pie, runs on Linux as well</p>
+ </div>
+ <a href="/docs/quickstart/android-tracing" class="button">Get Started</a>
+ </div>
+ </section>
+ </div>
+
+ <div class="section-wrapper">
+ <section class="home-section" id="tracing">
+ <img src="/assets/app_tracing.png" alt="Tracing illustration">
+ <div>
+ <h2>App Tracing</h2>
+ <div class="home-item">
+ <i class="material-icons-round">developer_mode</i>
+ <h3>Efficient trace point instrumentation</h3>
+ <p>Log your C++ app’s activity with high throughput, low overhead trace points</p>
+ </div>
+ <div class="home-item">
+ <i class="material-icons-round">ballot</i>
+ <h3>Structured and configurable events</h3>
+ <p>Define custom protobuf messages to represent strongly-typed app-specific information, trace only what you need</p>
+ </div>
+ <div class="home-item">
+ <i class="material-icons-round">view_compact</i>
+ <h3>Integrated with system-wide tracing</h3>
+ <p>Correlate your app’s state with system-wide profiling data on the same timeline</p>
+ </div>
+ <a href="/docs/instrumentation/tracing-sdk" class="button">Get Started</a>
+ </div>
+ </section>
+ </div>
+
+ <div class="section-wrapper">
+ <section class="home-section" id="viewer">
+ <img src="/assets/ui.png" alt="Trace viewer illustration">
+ <div>
+ <h2>Trace Viewer</h2>
+ <div class="home-item">
+ <i class="material-icons-round">timeline</i>
+ <h3>Interactive trace exploration</h3>
+ <p>Record, view and process trace data with the Perfetto UI</p>
+ </div>
+ <div class="home-item">
+ <i class="material-icons-round">file_copy</i>
+ <h3>Supports popular trace format files</h3>
+ <p>TraceEvent JSON, Android systrace, ftrace text output</p>
+ </div>
+ <div class="home-item">
+ <i class="material-icons-round">offline_bolt</i>
+ <h3>Runs fully in your browser</h3>
+ <p>No server interaction involved, works even if you are offline</p>
+ </div>
+ <div>
+ <a href="//ui.perfetto.dev" class="button">Open the UI</a>
+ </div>
+ </div>
+ </section>
+ </div>
+
+ <section class="home-section" id="analysis">
+ <img src="/assets/analysis.png" alt="Trace analysis illustration">
+ <div>
+ <h2>Trace Analysis</h2>
+ <div class="home-item">
+ <i class="material-icons-round">storage</i>
+ <h3>SQL-based trace model</h3>
+ <p>Trace processor ingests traces and exposes a SQLite-based
+ interface to access the contents of the trace, both via shell and UI
+ </p>
+ </div>
+ <div class="home-item">
+ <i class="material-icons-round">speed</i>
+ <h3>Large trace analysis</h3>
+ <p>
+ Supports traces up to tenths of GBs
+ </p>
+ </div>
+ <div class="home-item">
+ <i class="material-icons-round">compare_arrows</i>
+ <h3>Interoperable</h3>
+ <p>Can import and export popular trace formats: Chromium JSON trace format, Android Systrace, ftrace, CSV</p>
+ </div>
+ <div>
+ <a href="/docs/quickstart/trace-analysis" class="button">Learn more</a>
+ </div>
+ </div>
+ </section>
+
+ <%- include('template_footer.html'); -%>
+</main>
+
+</body>
+</html>
+
diff --git a/infra/perfetto.dev/src/template_markdown.html b/infra/perfetto.dev/src/template_markdown.html
new file mode 100644
index 0000000..feeaaa5
--- /dev/null
+++ b/infra/perfetto.dev/src/template_markdown.html
@@ -0,0 +1,17 @@
+<%- include('template_header.html'); -%>
+<div class="docs">
+ <nav class="nav">
+ <%- nav %>
+ </nav>
+
+ <main class="doc" data-md-file="<%= fileName %>">
+ <%- markdown %>
+ </main>
+
+ <section class="toc"></section>
+
+ <%- include('template_footer.html'); -%>
+</div>
+
+</body>
+</html>