diff --git a/Lib/profiling/sampling/flamegraph.css b/Lib/profiling/sampling/flamegraph.css index 67754ca609aa43..6baa0350730db6 100644 --- a/Lib/profiling/sampling/flamegraph.css +++ b/Lib/profiling/sampling/flamegraph.css @@ -1,31 +1,353 @@ +/* ======================================== + CSS CUSTOM PROPERTIES & THEMING + ======================================== */ + +:root { + /* Color Palette - Light Mode */ + --color-primary: #3776ab; + --color-primary-dark: #2d5aa0; + --color-primary-light: #4584bb; + --color-accent: #ffd43b; + --color-accent-dark: #ffcd02; + + /* Semantic Colors */ + --color-bg-main: #ffffff; + --color-bg-secondary: #f8f9fa; + --color-bg-tertiary: #e9ecef; + --color-surface: #ffffff; + --color-border: #e9ecef; + --color-border-hover: #d0d5dd; + + /* Text Colors */ + --color-text-primary: #2e3338; + --color-text-secondary: #5a6c7d; + --color-text-muted: #8c9ba8; + --color-text-inverse: #ffffff; + + /* Status Colors */ + --color-success: #28a745; + --color-warning: #dc3545; + --color-info: #17a2b8; + + /* Shadows */ + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); + --shadow-xl: 0 12px 36px rgba(0, 0, 0, 0.16); + + /* Spacing */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 24px; + --spacing-xl: 32px; + --spacing-2xl: 48px; + + /* Border Radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-full: 9999px; + + /* Typography */ + --font-family-base: "Source Sans Pro", "Segoe UI", -apple-system, BlinkMacSystemFont, "Roboto", sans-serif; + --font-family-mono: "SF Mono", "Monaco", "Consolas", "Courier New", monospace; + + --font-size-xs: 12px; + --font-size-sm: 13px; + --font-size-base: 14px; + --font-size-md: 15px; + --font-size-lg: 16px; + --font-size-xl: 18px; + --font-size-2xl: 22px; + --font-size-3xl: 28px; + + /* Transitions */ + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1); + + /* Animations */ + --tooltip-delay: 0.2s; + + /* Z-Index Scale */ + --z-base: 1; + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal-backdrop: 1040; + --z-modal: 1050; + --z-tooltip: 1070; +} + +/* ======================================== + BASE STYLES + ======================================== */ + +* { + box-sizing: border-box; +} + body { - font-family: - "Source Sans Pro", "Lucida Grande", "Lucida Sans Unicode", "Geneva", - "Verdana", sans-serif; + font-family: var(--font-family-base); margin: 0; padding: 0; - background: #ffffff; - color: #2e3338; + background: var(--color-bg-main); + color: var(--color-text-primary); line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ======================================== + SHARED COMPONENTS + ======================================== */ + +/* Shared Card Styles */ +.card { + background: var(--color-surface); + border-radius: var(--radius-lg); + border: 1px solid var(--color-border); + padding: var(--spacing-md); + box-shadow: var(--shadow-sm); + transition: all var(--transition-base); +} + +.card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +/* Section Headings */ +.section-heading { + margin: var(--spacing-xl) 0 var(--spacing-md) 0; + color: var(--color-text-primary); + font-size: var(--font-size-2xl); + font-weight: 600; + letter-spacing: -0.3px; +} + +/* Screen Reader Only */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +/* ======================================== + FOCUS STATES & ACCESSIBILITY + ======================================== */ + +*:focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +button:focus-visible, +a:focus-visible, +input:focus-visible, +select:focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +/* ======================================== + MODAL STYLES + ======================================== */ + +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + z-index: var(--z-modal); + align-items: center; + justify-content: center; + animation: fadeIn var(--transition-base); +} + +.modal.active { + display: flex; +} + +.modal-content { + background: var(--color-surface); + border-radius: var(--radius-xl); + max-width: 600px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + box-shadow: var(--shadow-xl); + animation: slideInScale var(--transition-slow); +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-lg); + border-bottom: 1px solid var(--color-border); +} + +.modal-header h2 { + margin: 0; + font-size: var(--font-size-2xl); + color: var(--color-text-primary); + font-weight: 600; +} + +.modal-close { + background: transparent; + border: none; + font-size: 32px; + color: var(--color-text-secondary); + cursor: pointer; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); + transition: all var(--transition-fast); + line-height: 1; + padding: 0; +} + +.modal-close:hover { + background: var(--color-bg-secondary); + color: var(--color-text-primary); +} + +.modal-body { + padding: var(--spacing-lg); +} + +.shortcut-section { + margin-bottom: var(--spacing-xl); +} + +.shortcut-section:last-child { + margin-bottom: 0; +} + +.shortcut-section h3 { + margin: 0 0 var(--spacing-md) 0; + font-size: var(--font-size-lg); + color: var(--color-text-primary); + font-weight: 600; +} + +.shortcut-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-sm) 0; + color: var(--color-text-secondary); + gap: var(--spacing-md); +} + +.shortcut-row kbd { + background: var(--color-bg-secondary); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + padding: var(--spacing-xs) var(--spacing-sm); + font-family: var(--font-family-mono); + font-size: var(--font-size-sm); + font-weight: 600; + color: var(--color-text-primary); + box-shadow: 0 1px 0 var(--color-border), + 0 2px 2px rgba(0, 0, 0, 0.05); + min-width: 32px; + text-align: center; + display: inline-block; +} + +/* ======================================== + LOADING STATES + ======================================== */ + +.loading-skeleton { + background: linear-gradient( + 90deg, + var(--color-bg-secondary) 0%, + var(--color-bg-tertiary) 50%, + var(--color-bg-secondary) 100% + ); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; + border-radius: var(--radius-sm); + display: inline-block; + height: 1em; + width: 100px; +} + +@keyframes shimmer { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideInScale { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } } +/* ======================================== + HEADER STYLES + ======================================== */ + .header { - background: linear-gradient(135deg, #3776ab 0%, #4584bb 100%); - color: white; - padding: 32px 0; - box-shadow: 0 2px 10px rgba(55, 118, 171, 0.2); + background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light) 100%); + color: var(--color-text-inverse); + padding: var(--spacing-xl) 0; + box-shadow: var(--shadow-md); } .header-content { max-width: 1200px; margin: 0 auto; - padding: 0 24px; + padding: 0 var(--spacing-lg); display: flex; - flex-direction: column; align-items: center; - justify-content: center; - text-align: center; - gap: 20px; + justify-content: space-between; + gap: var(--spacing-lg); +} + +@media (max-width: 768px) { + .header-content { + flex-direction: column; + text-align: center; + } } .python-logo { @@ -56,28 +378,76 @@ body { } .header-text .subtitle { - margin: 8px 0 0 0; - font-size: 1.1em; + margin: var(--spacing-sm) 0 0 0; + font-size: var(--font-size-lg); color: rgba(255, 255, 255, 0.9); font-weight: 300; } +.header-actions { + display: flex; + align-items: center; + gap: var(--spacing-md); +} + +@media (max-width: 768px) { + .header-actions { + width: 100%; + flex-direction: column; + } +} + .header-search { - width: 100%; + flex: 1; + min-width: 300px; max-width: 500px; } +@media (max-width: 768px) { + .header-search { + width: 100%; + max-width: none; + } +} + +/* Icon Buttons */ +.icon-btn { + width: 44px; + height: 44px; + background: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all var(--transition-fast); + font-size: var(--font-size-xl); + backdrop-filter: blur(10px); +} + +.icon-btn:hover { + background: rgba(255, 255, 255, 0.25); + border-color: rgba(255, 255, 255, 0.4); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.icon-btn:active { + transform: translateY(0); +} + .header-search #search-input { width: 100%; padding: 12px 20px; border: 2px solid rgba(255, 255, 255, 0.2); - border-radius: 25px; - font-size: 16px; + border-radius: var(--radius-full); + font-size: var(--font-size-lg); font-family: inherit; background: rgba(255, 255, 255, 0.95); - color: #2e3338; - transition: all 0.3s ease; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + color: var(--color-text-primary); + transition: all var(--transition-base); + box-shadow: var(--shadow-md); backdrop-filter: blur(10px); } @@ -90,40 +460,57 @@ body { } .header-search #search-input::placeholder { - color: #6c757d; + color: var(--color-text-muted); } +/* ======================================== + STATS SECTION + ======================================== */ + .stats-section { - background: #ffffff; - padding: 24px 0; - border-bottom: 1px solid #e9ecef; + background: var(--color-bg-secondary); + padding: var(--spacing-lg) 0; + border-bottom: 1px solid var(--color-border); } .stats-container { - max-width: 1200px; + max-width: 1400px; margin: 0 auto; - padding: 0 24px; + padding: 0 var(--spacing-lg); display: grid; grid-template-columns: repeat(3, 1fr); - gap: 20px; + gap: var(--spacing-lg); +} + +@media (max-width: 1200px) { + .stats-container { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + .stats-container { + grid-template-columns: 1fr; + } } .stat-card { - background: #ffffff; - border: 1px solid #e9ecef; - border-radius: 12px; - padding: 20px; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); display: flex; align-items: flex-start; - gap: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - transition: all 0.2s ease; + gap: var(--spacing-md); + box-shadow: var(--shadow-sm); + transition: all var(--transition-fast); min-height: 120px; } .stat-card:hover { - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); + box-shadow: var(--shadow-md); transform: translateY(-2px); + border-color: var(--color-border-hover); } .stat-icon { @@ -486,3 +873,699 @@ body { font-size: 12px !important; } } + +/* Tabs */ +.tabs-container { + background: #f8f9fa; + border-bottom: 2px solid #e9ecef; + position: sticky; + top: 0; + z-index: 100; +} + +.tabs { + max-width: 1400px; + margin: 0 auto; + display: flex; + gap: 4px; + padding: 0 24px; +} + +.tab-btn { + background: transparent; + border: none; + padding: 16px 24px; + font-size: 15px; + font-weight: 600; + color: #5a6c7d; + cursor: pointer; + transition: all 0.2s ease; + border-bottom: 3px solid transparent; + font-family: inherit; +} + +.tab-btn:hover { + color: #3776ab; + background: rgba(55, 118, 171, 0.05); +} + +.tab-btn.active { + color: #3776ab; + border-bottom-color: #3776ab; + background: white; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block !important; +} + +/* Ensure hidden attribute always works for accessibility */ +.tab-content[hidden] { + display: none !important; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Content wrapper for tabs */ +.content-wrapper { + max-width: 1400px; + margin: 0 auto; + padding: 32px 24px; +} + +/* Statistics Table */ +.table-controls { + display: flex; + gap: 12px; + margin-bottom: 24px; + align-items: center; + flex-wrap: wrap; +} + +.table-search { + flex: 1; + min-width: 250px; + padding: 12px 20px; + border: 2px solid #e9ecef; + border-radius: 8px; + font-size: 14px; + font-family: inherit; + transition: all 0.2s ease; +} + +.table-search:focus { + outline: none; + border-color: #3776ab; + box-shadow: 0 0 0 3px rgba(55, 118, 171, 0.1); +} + +.table-sort { + padding: 12px 16px; + border: 2px solid #e9ecef; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + font-family: inherit; + background: white; + cursor: pointer; + transition: all 0.2s ease; +} + +.table-sort:focus { + outline: none; + border-color: #3776ab; +} + +.export-btn { + padding: 12px 20px; + background: #ffd43b; + color: #2e3338; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; +} + +.export-btn:hover { + background: #ffcd02; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(255, 212, 59, 0.3); +} + +.table-container { + background: white; + border-radius: 12px; + border: 1px solid #e9ecef; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +} + +.data-table { + width: 100%; + border-collapse: collapse; +} + +.data-table thead { + background: linear-gradient(135deg, #3776ab 0%, #4584bb 100%); + color: white; +} + +.data-table th { + padding: 16px; + text-align: left; + font-weight: 600; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.data-table tbody tr { + border-bottom: 1px solid #f8f9fa; + transition: background 0.15s ease; +} + +.data-table tbody tr:hover { + background: #f8f9fa; +} + +.data-table td { + padding: 16px; + font-size: 14px; + color: #2e3338; +} + +.data-table td.loading { + text-align: center; + color: #5a6c7d; + font-style: italic; + padding: 32px; +} + +.rank-badge { + display: inline-block; + width: 32px; + height: 32px; + line-height: 32px; + text-align: center; + background: #3776ab; + color: white; + border-radius: 50%; + font-weight: 700; + font-size: 12px; +} + +.func-name { + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + color: #3776ab; + font-weight: 600; +} + +.file-location { + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + color: #5a6c7d; + font-size: 12px; +} + +.percent-bar { + display: flex; + align-items: center; + gap: 8px; +} + +.percent-bar-fill { + flex: 1; + height: 8px; + background: #f8f9fa; + border-radius: 4px; + overflow: hidden; + position: relative; +} + +.percent-bar-value { + height: 100%; + background: linear-gradient(90deg, #3776ab 0%, #4584bb 100%); + border-radius: 4px; + transition: width 0.3s ease; +} + +.percent-text { + font-weight: 600; + color: #3776ab; + min-width: 50px; + text-align: right; +} + +.action-btn { + padding: 6px 12px; + background: #3776ab; + color: white; + border: none; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.action-btn:hover { + background: #2d5aa0; + transform: scale(1.05); +} + +/* Insights Grid */ +.insights-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; + max-width: 1200px; + margin: 0 auto; +} + +@media (max-width: 1024px) { + .insights-grid { + grid-template-columns: 1fr; + } +} + +.insight-card { + background: white; + border-radius: 12px; + border: 1px solid #e9ecef; + padding: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + transition: all 0.2s ease; +} + +.insight-card:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + transform: translateY(-2px); +} + +.insight-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 2px solid #f8f9fa; +} + +.insight-icon { + font-size: 28px; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #3776ab 0%, #4584bb 100%); + border-radius: 12px; + flex-shrink: 0; +} + +.insight-header h3 { + margin: 0; + color: #2e3338; + font-size: 18px; + font-weight: 600; +} + +.insight-content { + color: #5a6c7d; + line-height: 1.7; + font-size: 14px; +} + +.insight-item { + padding: 12px; + background: #f8f9fa; + border-radius: 8px; + margin-bottom: 12px; + border-left: 4px solid #3776ab; +} + +.insight-item:last-child { + margin-bottom: 0; +} + +.insight-title { + font-weight: 600; + color: #2e3338; + margin-bottom: 4px; +} + +.insight-description { + color: #5a6c7d; + font-size: 13px; + line-height: 1.5; +} + +.insight-metric { + display: inline-block; + padding: 4px 8px; + background: #3776ab; + color: white; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + margin-right: 8px; +} + +/* Modules Tab */ +.module-header { + text-align: center; + margin-bottom: 32px; +} + +.module-header h2 { + color: #2e3338; + font-size: 28px; + font-weight: 600; + margin: 0 0 8px 0; +} + +.module-subtitle { + color: #5a6c7d; + font-size: 16px; + margin: 0; +} + +.modules-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 32px; +} + +@media (max-width: 1024px) { + .modules-grid { + grid-template-columns: 1fr; + } +} + +.module-chart-container { + background: white; + border-radius: 12px; + border: 1px solid #e9ecef; + padding: 24px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +} + +.module-list-container { + background: white; + border-radius: 12px; + border: 1px solid #e9ecef; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +} + +/* Export Grid */ +.export-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 24px; +} + +.export-card { + background: white; + border-radius: 12px; + border: 1px solid #e9ecef; + padding: 32px; + text-align: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + transition: all 0.2s ease; +} + +.export-card:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + transform: translateY(-4px); +} + +.export-icon { + font-size: 48px; + margin-bottom: 16px; +} + +.export-card h3 { + color: #2e3338; + font-size: 18px; + font-weight: 600; + margin: 0 0 12px 0; +} + +.export-card p { + color: #5a6c7d; + font-size: 14px; + line-height: 1.6; + margin: 0 0 24px 0; +} + +.export-action-btn { + padding: 12px 24px; + background: #3776ab; + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; + width: 100%; +} + +.export-action-btn:hover { + background: #2d5aa0; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(55, 118, 171, 0.3); +} + +/* Scrollbar styling */ +.table-container::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.table-container::-webkit-scrollbar-track { + background: #f8f9fa; +} + +.table-container::-webkit-scrollbar-thumb { + background: #3776ab; + border-radius: 4px; +} + +.table-container::-webkit-scrollbar-thumb:hover { + background: #2d5aa0; +} + +/* Stats Overview Grid */ +.stats-overview-grid { + display: flex; + flex-direction: column; + gap: var(--spacing-xl); + margin-bottom: var(--spacing-xl); +} + +.stats-section-wrapper { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.stats-section-title { + font-size: var(--font-size-lg); + font-weight: 600; + color: var(--color-text-primary); + margin: 0; + padding-bottom: var(--spacing-sm); + border-bottom: 2px solid var(--color-border); +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 250px)); + gap: var(--spacing-md); +} + +.stat-metric-card { + /* Inherits from .card */ + max-width: 100%; +} + +.stat-metric-label { + font-size: 12px; + color: #5a6c7d; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 6px; +} + +.stat-help-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + border-radius: 50%; + background: #e9ecef; + color: #5a6c7d; + font-size: 11px; + font-weight: 700; + cursor: help; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.stat-help-icon:hover { + background: #3776ab; + color: white; + transform: scale(1.1); +} + +.stat-metric-value { + font-size: 28px; + font-weight: 700; + color: #3776ab; + line-height: 1.2; +} + +.stat-metric-description { + font-size: 13px; + color: #5a6c7d; + margin-top: 6px; +} + +/* Module type badges */ +.module-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 10px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-left: 8px; + vertical-align: middle; +} + +.module-badge-stdlib { + background: #3776ab; + color: white; +} + +.module-badge-site-packages { + background: #ffd43b; + color: #2e3338; +} + +.module-badge-project { + background: #28a745; + color: white; +} + +.module-badge-other { + background: #e9ecef; + color: #5a6c7d; +} + +/* Profile Info Grid */ +.profile-info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 24px; +} + +.profile-info-section { + background: var(--color-surface); + border-radius: var(--radius-lg); + border: 1px solid var(--color-border); + overflow: hidden; + box-shadow: var(--shadow-sm); + transition: all var(--transition-base); + padding: 0; +} + +.profile-info-section:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.profile-info-section-header { + background: linear-gradient(135deg, #3776ab 0%, #4584bb 100%); + color: white; + padding: 16px 20px; + display: flex; + align-items: center; + gap: 12px; +} + +.profile-info-section-icon { + font-size: 24px; +} + +.profile-info-section-title { + margin: 0; + font-size: 16px; + font-weight: 600; + letter-spacing: 0.3px; +} + +.profile-info-section-content { + padding: 20px; +} + +.profile-info-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f5f6f7; +} + +.profile-info-row:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.profile-info-row:first-child { + padding-top: 0; +} + +.profile-info-label { + font-size: 13px; + color: #5a6c7d; + font-weight: 500; + display: flex; + align-items: center; + gap: 6px; + min-width: 140px; +} + +.profile-info-value { + font-size: 15px; + color: #2e3338; + font-weight: 600; + text-align: right; + word-break: break-word; + flex: 1; +} + +.profile-info-value-highlight { + color: #3776ab; + font-size: 16px; + font-weight: 700; +} + +.profile-info-value-success { + color: #28a745; + font-weight: 700; +} + +.profile-info-value-warn { + color: #dc3545; + font-weight: 700; +} + +.profile-info-value-mono { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + background: #f8f9fa; + padding: 4px 8px; + border-radius: 4px; +} diff --git a/Lib/profiling/sampling/heatmap.css b/Lib/profiling/sampling/heatmap.css new file mode 100644 index 00000000000000..94e0755de1944f --- /dev/null +++ b/Lib/profiling/sampling/heatmap.css @@ -0,0 +1,718 @@ +/* ======================================== + HEATMAP REPORT STYLES - Tachyon Profiler + Uses shared design system from flamegraph + ======================================== */ + +:root { + /* Color palette */ + --color-primary: #3776ab; + --color-primary-dark: #2d5aa0; + --color-accent: #ffd43b; + --color-bg-main: #ffffff; + --color-bg-secondary: #f8f9fa; + --color-surface: #ffffff; + --color-border: #e9ecef; + --color-text-primary: #2e3338; + --color-text-secondary: #5a6c7d; + --color-text-muted: #8c9ba8; + --color-text-inverse: #ffffff; + + /* Common gradients */ + --gradient-primary: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%); + + /* Elevation & effects */ + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); + + /* Border radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + + /* Spacing system */ + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 24px; + --spacing-xl: 32px; + + /* Typography */ + --font-family-base: "Source Sans Pro", "Segoe UI", -apple-system, BlinkMacSystemFont, sans-serif; + --font-family-mono: "SF Mono", "Monaco", "Consolas", "Courier New", monospace; + + /* Animation */ + --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1); + + /* Code view specific */ + --code-bg: #ffffff; + --code-bg-line: #f8f9fa; + --code-border: #e9ecef; + --code-text: #2e3338; + --code-text-muted: #8c9ba8; + --code-accent: #3776ab; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: var(--font-family-base); + line-height: 1.6; + color: var(--color-text-primary); + background: var(--color-bg-main); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ======================================== + INDEX PAGE STYLES + ======================================== */ + +.page-wrapper { + min-height: 100vh; + background: var(--gradient-primary); + padding: var(--spacing-lg); +} + +.container { + max-width: 1400px; + margin: 0 auto; + background: var(--color-surface); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + overflow: hidden; +} + +/* Header */ +.header { + background: var(--gradient-primary); + color: var(--color-text-inverse); + padding: var(--spacing-xl); + text-align: center; +} + +.header-content { + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-md); + flex-wrap: wrap; +} + +.python-logo { + width: 60px; + height: 60px; +} + +.header-text h1 { + font-size: 2.5em; + margin-bottom: var(--spacing-sm); + font-weight: 700; + letter-spacing: -0.5px; +} + +.subtitle { + font-size: 1.1em; + opacity: 0.95; + font-weight: 400; +} + +/* Stats Summary Cards */ +.stats-summary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--spacing-lg); + padding: var(--spacing-xl); + background: var(--color-bg-secondary); + border-bottom: 1px solid var(--color-border); +} + +.stat-card { + background: var(--color-surface); + padding: var(--spacing-lg); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + text-align: center; + transition: all var(--transition-base); +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.stat-value { + font-size: 2em; + font-weight: 700; + color: var(--color-primary); + display: block; + margin-bottom: var(--spacing-sm); +} + +.stat-label { + color: var(--color-text-secondary); + font-size: 0.9em; +} + +/* File List Section */ +.file-list { + padding: var(--spacing-xl); +} + +.file-list h2 { + margin-bottom: var(--spacing-lg); + color: var(--color-text-primary); + font-size: 1.8em; + font-weight: 600; +} + +/* Filter Controls */ +.filter-controls { + margin-bottom: var(--spacing-lg); + display: flex; + gap: var(--spacing-md); + flex-wrap: wrap; +} + +.filter-controls input, +.filter-controls select { + padding: 10px 15px; + border: 2px solid var(--color-border); + border-radius: var(--radius-sm); + font-size: 1em; + font-family: var(--font-family-base); + transition: all var(--transition-base); +} + +.filter-controls input { + flex: 1; + min-width: 250px; +} + +.filter-controls input:focus, +.filter-controls select:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(55, 118, 171, 0.1); +} + +/* Table Styles */ +table { + width: 100%; + border-collapse: collapse; + background: var(--color-surface); + border-radius: var(--radius-md); + overflow: hidden; +} + +thead { + background: var(--color-bg-secondary); + position: sticky; + top: 0; + z-index: 10; +} + +th { + padding: 15px; + text-align: left; + font-weight: 600; + color: var(--color-text-primary); + border-bottom: 2px solid var(--color-border); + cursor: pointer; + user-select: none; + transition: background var(--transition-base); +} + +th:hover { + background: var(--color-bg-main); +} + +td { + padding: 12px 15px; + border-bottom: 1px solid var(--color-bg-secondary); +} + +tbody tr { + transition: background var(--transition-base); +} + +tbody tr:hover { + background: var(--color-bg-secondary); +} + +/* Links */ +.file-link { + color: var(--color-primary); + text-decoration: none; + font-weight: 500; + transition: color var(--transition-base); +} + +.file-link:hover { + color: var(--color-primary-dark); + text-decoration: underline; +} + +/* Module Badges */ +.module-badge { + display: inline-block; + padding: 4px 10px; + border-radius: var(--radius-sm); + font-size: 0.85em; + font-weight: 500; +} + +.badge-stdlib { + background: #d4edda; + color: #155724; +} + +.badge-site-packages { + background: #cce5ff; + color: #004085; +} + +.badge-project { + background: #fff3cd; + color: #856404; +} + +.badge-other { + background: #e2e3e5; + color: #383d41; +} + +/* Heatmap Bar */ +.heatmap-bar { + display: inline-block; + height: 20px; + background: linear-gradient(90deg, #00d4ff 0%, #ff6b00 100%); + border-radius: var(--radius-sm); + min-width: 2px; + transition: transform var(--transition-base); +} + +.heatmap-bar:hover { + transform: scaleY(1.2); +} + +/* Footer */ +footer { + padding: var(--spacing-lg); + text-align: center; + color: var(--color-text-secondary); + font-size: 0.9em; + background: var(--color-bg-secondary); + border-top: 1px solid var(--color-border); +} + +/* ======================================== + FILE VIEW STYLES (Code Display) + ======================================== */ + +.code-view { + font-family: var(--font-family-mono); + background: var(--color-bg-main); + color: var(--code-text); + min-height: 100vh; +} + +/* Code Header */ +.code-header { + background: var(--gradient-primary); + color: var(--color-text-inverse); + padding: var(--spacing-lg) var(--spacing-xl); + position: sticky; + top: 0; + z-index: 100; + box-shadow: var(--shadow-md); +} + +.code-header-content { + max-width: 1600px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--spacing-md); +} + +.code-header h1 { + font-size: 1.5em; + font-weight: 600; + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.back-link { + color: var(--color-text-inverse); + text-decoration: none; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.15); + border-radius: var(--radius-sm); + font-size: 0.9em; + transition: all var(--transition-base); + font-family: var(--font-family-base); +} + +.back-link:hover { + background: rgba(255, 255, 255, 0.25); +} + +/* File Stats Bar */ +.file-stats { + background: var(--color-bg-secondary); + padding: var(--spacing-lg) var(--spacing-xl); + border-bottom: 1px solid var(--code-border); +} + +.stats-grid { + max-width: 1600px; + margin: 0 auto; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: var(--spacing-lg); +} + +.stat-item { + background: var(--color-surface); + padding: var(--spacing-lg); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + text-align: center; + transition: all var(--transition-base); +} + +.stat-item:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.stat-item .stat-value { + font-size: 1.8em; + font-weight: 700; + color: var(--code-accent); +} + +.stat-item .stat-label { + color: var(--code-text-muted); + font-size: 0.85em; + margin-top: 4px; +} + +/* Legend */ +.legend { + background: var(--color-bg-secondary); + padding: 15px var(--spacing-xl); + border-bottom: 1px solid var(--code-border); +} + +.legend-content { + max-width: 1600px; + margin: 0 auto; + display: flex; + align-items: center; + gap: var(--spacing-xl); +} + +.legend-title { + font-weight: 600; + color: var(--color-text-primary); +} + +.legend-gradient { + flex: 1; + max-width: 400px; + height: 30px; + background: linear-gradient(90deg, + rgba(240, 240, 240, 1) 0%, + rgba(0, 150, 255, 0.2) 25%, + rgba(0, 255, 150, 0.3) 50%, + rgba(255, 200, 0, 0.4) 75%, + rgba(255, 100, 0, 0.6) 90%, + rgba(255, 0, 0, 0.75) 100% + ); + border-radius: var(--radius-sm); + border: 1px solid var(--code-border); +} + +.legend-labels { + display: flex; + gap: 15px; + font-size: 0.85em; + color: var(--code-text-muted); +} + +/* Code Container */ +.code-container { + max-width: 1600px; + margin: var(--spacing-lg) auto; + background: var(--color-surface); + border: 1px solid var(--code-border); + border-radius: var(--radius-md); + overflow: hidden; + box-shadow: var(--shadow-sm); +} + +/* Code Lines */ +.code-line { + position: relative; + display: flex; + min-height: 20px; + line-height: 20px; + font-size: 14px; + transition: background var(--transition-base); + scroll-margin-top: 50vh; +} + +.code-line:hover { + filter: brightness(0.98); +} + +.line-number { + flex-shrink: 0; + width: 60px; + padding: 0 10px; + text-align: right; + color: var(--code-text-muted); + background: var(--code-bg-line); + border-right: 1px solid var(--code-border); + user-select: none; + transition: all var(--transition-base); +} + +.line-number:hover { + background: var(--color-primary); + color: var(--color-text-inverse); +} + +.line-samples { + flex-shrink: 0; + width: 100px; + padding: 0 10px; + text-align: right; + color: var(--code-accent); + background: var(--code-bg-line); + border-right: 1px solid var(--code-border); + font-weight: 600; + user-select: none; +} + +.line-content { + flex: 1; + padding: 0 15px; + white-space: pre; + overflow-x: auto; +} + +/* Scrollbar Styling */ +.line-content::-webkit-scrollbar { + height: 6px; +} + +.line-content::-webkit-scrollbar-thumb { + background: var(--code-border); + border-radius: var(--radius-sm); +} + +.line-content::-webkit-scrollbar-thumb:hover { + background: #4e4e52; +} + +/* Navigation Buttons */ +.line-nav-buttons { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + display: flex; + gap: 4px; + align-items: center; +} + +.nav-btn { + padding: 2px 8px; + font-size: 11px; + font-weight: 500; + border: 1px solid var(--code-border); + border-radius: var(--radius-sm); + background: var(--color-surface); + color: var(--color-primary); + cursor: pointer; + transition: all var(--transition-base); + font-family: var(--font-family-base); + user-select: none; +} + +.nav-btn:hover:not(:disabled) { + background: var(--color-primary); + color: var(--color-text-inverse); + transform: translateY(-1px); + box-shadow: var(--shadow-sm); +} + +.nav-btn:active:not(:disabled) { + transform: translateY(0); +} + +.nav-btn:disabled { + opacity: 0.3; + cursor: not-allowed; + color: var(--color-text-muted); + background: var(--color-bg-secondary); + border-color: var(--color-border); +} + +.nav-btn.caller { + color: #2563eb; +} + +.nav-btn.callee { + color: #dc2626; +} + +.nav-btn.caller:hover:not(:disabled) { + background: #2563eb; +} + +.nav-btn.callee:hover:not(:disabled) { + background: #dc2626; +} + +/* Highlighted target line */ +.code-line:target { + animation: highlight-line 2s ease-out; +} + +@keyframes highlight-line { + 0% { + background: rgba(255, 212, 59, 0.6) !important; + outline: 3px solid var(--color-accent); + outline-offset: -3px; + } + 50% { + background: rgba(255, 212, 59, 0.5) !important; + outline: 3px solid var(--color-accent); + outline-offset: -3px; + } + 100% { + background: inherit; + outline: 3px solid transparent; + outline-offset: -3px; + } +} + +/* Popup menu for multiple callees */ +.callee-menu { + position: absolute; + background: var(--color-surface); + border: 1px solid var(--code-border); + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + padding: var(--spacing-sm); + z-index: 1000; + min-width: 250px; + max-width: 400px; + max-height: 300px; + overflow-y: auto; +} + +.callee-menu-header { + font-weight: 600; + color: var(--color-text-primary); + margin-bottom: var(--spacing-sm); + padding-bottom: var(--spacing-sm); + border-bottom: 1px solid var(--code-border); +} + +.callee-menu-item { + padding: var(--spacing-sm); + margin: 4px 0; + border-radius: var(--radius-sm); + cursor: pointer; + transition: background var(--transition-base); + display: flex; + flex-direction: column; + gap: 4px; +} + +.callee-menu-item:hover { + background: var(--color-bg-secondary); +} + +.callee-menu-func { + font-weight: 500; + color: var(--color-primary); + font-family: var(--font-family-mono); + font-size: 0.9em; +} + +.callee-menu-file { + font-size: 0.85em; + color: var(--color-text-muted); +} + +/* Callee menu scrollbar styling */ +.callee-menu::-webkit-scrollbar { + width: 8px; +} + +.callee-menu::-webkit-scrollbar-track { + background: var(--color-bg-secondary); + border-radius: var(--radius-sm); +} + +.callee-menu::-webkit-scrollbar-thumb { + background: var(--code-border); + border-radius: var(--radius-sm); + transition: background var(--transition-base); +} + +.callee-menu::-webkit-scrollbar-thumb:hover { + background: var(--color-text-muted); +} + +/* ======================================== + SCROLL MINIMAP MARKER + ======================================== */ + +#scroll_marker { + position: fixed; + z-index: 1000; + right: 0; + top: 0; + width: 16px; + height: 100%; + background: var(--color-surface); + border-left: 1px solid var(--code-border); + will-change: transform; + pointer-events: none; +} + +#scroll_marker .marker { + position: absolute; + min-height: 3px; + width: 100%; + pointer-events: none; +} + +/* Marker colors based on sample intensity */ +#scroll_marker .marker.cold { + background: rgba(100, 150, 255, 0.4); +} + +#scroll_marker .marker.warm { + background: rgba(0, 200, 100, 0.5); +} + +#scroll_marker .marker.hot { + background: rgba(255, 150, 0, 0.6); +} + +#scroll_marker .marker.vhot { + background: rgba(255, 50, 0, 0.8); +} diff --git a/Lib/profiling/sampling/heatmap.js b/Lib/profiling/sampling/heatmap.js new file mode 100644 index 00000000000000..dfdd7e3fb0c30f --- /dev/null +++ b/Lib/profiling/sampling/heatmap.js @@ -0,0 +1,184 @@ +// Tachyon Profiler - Heatmap JavaScript +// Interactive features for the heatmap visualization + +// Apply background colors on page load +document.addEventListener('DOMContentLoaded', function() { + // Apply background colors + document.querySelectorAll('.code-line[data-bg-color]').forEach(line => { + const bgColor = line.getAttribute('data-bg-color'); + if (bgColor) { + line.style.background = bgColor; + } + }); +}); + +// State management +let currentMenu = null; + +// Utility: Create element with class and content +function createElement(tag, className, textContent = '') { + const el = document.createElement(tag); + if (className) el.className = className; + if (textContent) el.textContent = textContent; + return el; +} + +// Utility: Calculate smart menu position +function calculateMenuPosition(buttonRect, menuWidth, menuHeight) { + const viewport = { width: window.innerWidth, height: window.innerHeight }; + const scroll = { + x: window.pageXOffset || document.documentElement.scrollLeft, + y: window.pageYOffset || document.documentElement.scrollTop + }; + + const left = buttonRect.right + menuWidth + 10 < viewport.width + ? buttonRect.right + scroll.x + 10 + : Math.max(scroll.x + 10, buttonRect.left + scroll.x - menuWidth - 10); + + const top = buttonRect.bottom + menuHeight + 10 < viewport.height + ? buttonRect.bottom + scroll.y + 5 + : Math.max(scroll.y + 10, buttonRect.top + scroll.y - menuHeight - 10); + + return { left, top }; +} + +// Close and remove current menu +function closeMenu() { + if (currentMenu) { + currentMenu.remove(); + currentMenu = null; + } +} + +// Show navigation menu for multiple options +function showNavigationMenu(button, items, title) { + closeMenu(); + + const menu = createElement('div', 'callee-menu'); + menu.appendChild(createElement('div', 'callee-menu-header', title)); + + items.forEach(linkData => { + const item = createElement('div', 'callee-menu-item'); + item.appendChild(createElement('div', 'callee-menu-func', linkData.func)); + item.appendChild(createElement('div', 'callee-menu-file', linkData.file)); + item.addEventListener('click', () => window.location.href = linkData.link); + menu.appendChild(item); + }); + + const pos = calculateMenuPosition(button.getBoundingClientRect(), 350, 300); + menu.style.left = `${pos.left}px`; + menu.style.top = `${pos.top}px`; + + document.body.appendChild(menu); + currentMenu = menu; +} + +// Handle navigation button clicks +function handleNavigationClick(button, e) { + e.stopPropagation(); + + const navData = button.getAttribute('data-nav'); + if (navData) { + window.location.href = JSON.parse(navData).link; + return; + } + + const navMulti = button.getAttribute('data-nav-multi'); + if (navMulti) { + const items = JSON.parse(navMulti); + const title = button.classList.contains('caller') ? 'Choose a caller:' : 'Choose a callee:'; + showNavigationMenu(button, items, title); + } +} + +// Initialize navigation buttons +document.querySelectorAll('.nav-btn').forEach(button => { + button.addEventListener('click', e => handleNavigationClick(button, e)); +}); + +// Close menu when clicking outside +document.addEventListener('click', e => { + if (currentMenu && !currentMenu.contains(e.target) && !e.target.classList.contains('nav-btn')) { + closeMenu(); + } +}); + +// Scroll to target line (centered using CSS scroll-margin-top) +function scrollToTargetLine() { + if (!window.location.hash) return; + const target = document.querySelector(window.location.hash); + if (target) { + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } +} + +// Initialize line number permalink handlers +document.querySelectorAll('.line-number').forEach(lineNum => { + lineNum.style.cursor = 'pointer'; + lineNum.addEventListener('click', e => { + window.location.hash = `line-${e.target.textContent.trim()}`; + }); +}); + +// Setup scroll-to-line behavior +setTimeout(scrollToTargetLine, 100); +window.addEventListener('hashchange', () => setTimeout(scrollToTargetLine, 50)); + +// Get sample count from line element +function getSampleCount(line) { + const text = line.querySelector('.line-samples')?.textContent.trim().replace(/,/g, ''); + return parseInt(text) || 0; +} + +// Classify intensity based on ratio +function getIntensityClass(ratio) { + if (ratio > 0.75) return 'vhot'; + if (ratio > 0.5) return 'hot'; + if (ratio > 0.25) return 'warm'; + return 'cold'; +} + +// Build scroll minimap showing hotspot locations +function buildScrollMarker() { + const existing = document.getElementById('scroll_marker'); + if (existing) existing.remove(); + + if (document.body.scrollHeight <= window.innerHeight) return; + + const lines = document.querySelectorAll('.code-line'); + const markerScale = window.innerHeight / document.body.scrollHeight; + const lineHeight = Math.min(Math.max(3, window.innerHeight / lines.length), 10); + const maxSamples = Math.max(...Array.from(lines, getSampleCount)); + + const scrollMarker = createElement('div', ''); + scrollMarker.id = 'scroll_marker'; + + let prevLine = -99, lastMark, lastTop; + + lines.forEach((line, index) => { + const samples = getSampleCount(line); + if (samples === 0) return; + + const lineTop = Math.floor(line.offsetTop * markerScale); + const lineNumber = index + 1; + const intensityClass = maxSamples > 0 ? getIntensityClass(samples / maxSamples) : 'cold'; + + if (lineNumber === prevLine + 1 && lastMark?.classList.contains(intensityClass)) { + lastMark.style.height = `${lineTop + lineHeight - lastTop}px`; + } else { + lastMark = createElement('div', `marker ${intensityClass}`); + lastMark.style.height = `${lineHeight}px`; + lastMark.style.top = `${lineTop}px`; + scrollMarker.appendChild(lastMark); + lastTop = lineTop; + } + + prevLine = lineNumber; + }); + + document.body.appendChild(scrollMarker); +} + +// Build scroll marker on load and resize +setTimeout(buildScrollMarker, 200); +window.addEventListener('resize', buildScrollMarker); diff --git a/Lib/profiling/sampling/heatmap_index.js b/Lib/profiling/sampling/heatmap_index.js new file mode 100644 index 00000000000000..b575fef98ce570 --- /dev/null +++ b/Lib/profiling/sampling/heatmap_index.js @@ -0,0 +1,63 @@ +function filterTable() { + const searchTerm = document.getElementById('searchInput').value.toLowerCase(); + const moduleFilter = document.getElementById('moduleFilter').value; + const table = document.getElementById('fileTable'); + const rows = table.getElementsByTagName('tr'); + + for (let i = 1; i < rows.length; i++) { + const row = rows[i]; + const text = row.textContent.toLowerCase(); + const moduleType = row.getAttribute('data-module-type'); + + const matchesSearch = text.includes(searchTerm); + const matchesModule = moduleFilter === 'all' || moduleType === moduleFilter; + + row.style.display = (matchesSearch && matchesModule) ? '' : 'none'; + } +} + +// Track current sort state +let currentSortColumn = -1; +let currentSortAscending = true; + +function sortTable(columnIndex) { + const table = document.getElementById('fileTable'); + const tbody = table.querySelector('tbody'); + const rows = Array.from(tbody.querySelectorAll('tr')); + + // Determine sort direction + let ascending = true; + if (currentSortColumn === columnIndex) { + // Same column - toggle direction + ascending = !currentSortAscending; + } else { + // New column - default direction based on type + // For numeric columns (samples, lines, %), descending is default + // For text columns (file, module, type), ascending is default + ascending = columnIndex <= 2; // Columns 0-2 are text, 3+ are numeric + } + + rows.sort((a, b) => { + let aVal = a.cells[columnIndex].textContent.trim(); + let bVal = b.cells[columnIndex].textContent.trim(); + + // Try to parse as number + const aNum = parseFloat(aVal.replace(/,/g, '').replace('%', '')); + const bNum = parseFloat(bVal.replace(/,/g, '').replace('%', '')); + + let result; + if (!isNaN(aNum) && !isNaN(bNum)) { + result = aNum - bNum; // Numeric comparison + } else { + result = aVal.localeCompare(bVal); // String comparison + } + + return ascending ? result : -result; + }); + + rows.forEach(row => tbody.appendChild(row)); + + // Update sort state + currentSortColumn = columnIndex; + currentSortAscending = ascending; +} diff --git a/Lib/profiling/sampling/heatmap_index_template.html b/Lib/profiling/sampling/heatmap_index_template.html new file mode 100644 index 00000000000000..05238bdaa1598c --- /dev/null +++ b/Lib/profiling/sampling/heatmap_index_template.html @@ -0,0 +1,80 @@ + + +
+ + +Line-by-line performance analysis
+| File ⇅ | +Module ⇅ | +Type ⇅ | +Line Samples ⇅ | +Lines Hit ⇅ | +Intensity | +
|---|