diff --git a/Lib/profiling/sampling/flamegraph.css b/Lib/profiling/sampling/flamegraph.css index 0a6fde2ad32..1703815acd9 100644 --- a/Lib/profiling/sampling/flamegraph.css +++ b/Lib/profiling/sampling/flamegraph.css @@ -1,158 +1,1154 @@ -body { - font-family: - "Source Sans Pro", "Lucida Grande", "Lucida Sans Unicode", "Geneva", - "Verdana", sans-serif; +/* ========================================================================== + Flamegraph Viewer - CSS + Python-branded profiler with dark/light theme support + ========================================================================== */ + +/* -------------------------------------------------------------------------- + CSS Variables & Theme System + -------------------------------------------------------------------------- */ + +:root { + /* Typography */ + --font-sans: "Source Sans Pro", "Lucida Grande", "Lucida Sans Unicode", + "Geneva", "Verdana", sans-serif; + --font-mono: 'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', monospace; + + /* Python brand colors (theme-independent) */ + --python-blue: #3776ab; + --python-blue-light: #4584bb; + --python-blue-lighter: #5592cc; + --python-gold: #ffd43b; + --python-gold-dark: #ffcd02; + --python-gold-light: #ffdc5c; + + /* Heat palette - defined per theme below */ + + /* Layout */ + --sidebar-width: 280px; + --sidebar-collapsed: 44px; + --topbar-height: 52px; + --statusbar-height: 32px; + + /* Transitions */ + --transition-fast: 0.15s ease; + --transition-normal: 0.25s ease; +} + +/* Light theme (default) - Python yellow-to-blue heat palette */ +:root, [data-theme="light"] { + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --bg-tertiary: #e9ecef; + --border: #e9ecef; + --border-subtle: #f0f2f5; + + --text-primary: #2e3338; + --text-secondary: #5a6c7d; + --text-muted: #8b949e; + + --accent: #3776ab; + --accent-hover: #2d5aa0; + --accent-glow: rgba(55, 118, 171, 0.15); + + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15); + + --header-gradient: linear-gradient(135deg, #3776ab 0%, #4584bb 100%); + + /* Light mode heat palette - blue to yellow to orange to red (cold to hot) */ + --heat-1: #d6e9f8; + --heat-2: #a8d0ef; + --heat-3: #7ba3d1; + --heat-4: #ffe6a8; + --heat-5: #ffd43b; + --heat-6: #ffb84d; + --heat-7: #ff9966; + --heat-8: #ff6347; +} + +/* Dark theme - teal-to-orange heat palette */ +[data-theme="dark"] { + --bg-primary: #0d1117; + --bg-secondary: #161b22; + --bg-tertiary: #21262d; + --border: #30363d; + --border-subtle: #21262d; + + --text-primary: #e6edf3; + --text-secondary: #8b949e; + --text-muted: #6e7681; + + --accent: #58a6ff; + --accent-hover: #79b8ff; + --accent-glow: rgba(88, 166, 255, 0.15); + + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5); + + --header-gradient: linear-gradient(135deg, #21262d 0%, #30363d 100%); + + /* Dark mode heat palette - dark blue to teal to yellow to orange (cold to hot) */ + --heat-1: #1e3a5f; + --heat-2: #2d5580; + --heat-3: #4a7ba7; + --heat-4: #5a9fa8; + --heat-5: #7ec488; + --heat-6: #c4de6a; + --heat-7: #f4d44d; + --heat-8: #ff6b35; +} + +/* -------------------------------------------------------------------------- + Base Styles + -------------------------------------------------------------------------- */ + +*, *::before, *::after { + box-sizing: border-box; +} + +html, body { margin: 0; padding: 0; - background: #ffffff; - color: #2e3338; + height: 100%; + overflow: hidden; +} + +body { + font-family: var(--font-sans); + font-size: 14px; line-height: 1.6; + color: var(--text-primary); + background: var(--bg-primary); + transition: background var(--transition-normal), color var(--transition-normal); } -.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); -} +/* -------------------------------------------------------------------------- + Layout Structure + -------------------------------------------------------------------------- */ -.header-content { - max-width: 1200px; - margin: 0 auto; - padding: 0 24px; +.app-layout { display: flex; flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - gap: 20px; + height: 100vh; } -.python-logo { - width: auto; - height: 72px; - margin-bottom: 12px; /* tighter spacing to avoid visual gap */ +.main-content { + display: flex; + flex: 1; + min-height: 0; +} + +/* -------------------------------------------------------------------------- + Top Bar + -------------------------------------------------------------------------- */ + +.top-bar { + height: 56px; + background: var(--header-gradient); + display: flex; + align-items: center; + padding: 0 16px; + gap: 16px; flex-shrink: 0; + box-shadow: 0 2px 10px rgba(55, 118, 171, 0.25); + border-bottom: 2px solid var(--python-gold); +} + +/* Brand / Logo */ +.brand { + display: flex; + align-items: center; + gap: 12px; + color: white; + text-decoration: none; + flex-shrink: 0; +} + +.brand-logo { display: flex; align-items: center; justify-content: center; + width: 28px; + height: 28px; + flex-shrink: 0; } -.python-logo img { - height: 72px; - width: auto; - display: block; /* avoid baseline alignment issues */ - vertical-align: middle; - /* subtle shadow that does not affect layout */ - filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.1)); +/* Style the inlined SVG/img inside brand-logo */ +.brand-logo svg, +.brand-logo img { + width: 28px; + height: 28px; + display: block; + object-fit: contain; + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2)); } -.header-text h1 { - margin: 0; - font-size: 2.5em; - font-weight: 600; - color: white; - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +.brand-info { + display: flex; + flex-direction: column; + line-height: 1.15; } -.header-text .subtitle { - margin: 8px 0 0 0; - font-size: 1.1em; - color: rgba(255, 255, 255, 0.9); - font-weight: 300; -} - -.header-search { - width: 100%; - max-width: 500px; -} - -.header-search #search-input { - width: 100%; - padding: 12px 20px; - border: 2px solid rgba(255, 255, 255, 0.2); - border-radius: 25px; +.brand-text { + font-weight: 700; font-size: 16px; - font-family: inherit; - background: rgba(255, 255, 255, 0.95); + letter-spacing: -0.3px; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); +} + +.brand-subtitle { + font-weight: 500; + font-size: 10px; + opacity: 0.9; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.brand-divider { + width: 1px; + height: 16px; + background: rgba(255, 255, 255, 0.3); +} + +/* Search */ +.search-wrapper { + flex: 1; + max-width: 360px; + position: relative; +} + +.search-input { + width: 100%; + padding: 8px 36px 8px 14px; + font-family: var(--font-sans); + font-size: 13px; color: #2e3338; - transition: all 0.3s ease; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(10px); -} - -.header-search #search-input:focus { + background: rgba(255, 255, 255, 0.95); + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 20px; outline: none; - border-color: rgba(255, 255, 255, 0.8); - background: rgba(255, 255, 255, 1); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); - transform: translateY(-2px); + transition: all var(--transition-fast); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } -.header-search #search-input::placeholder { +.search-input::placeholder { color: #6c757d; } -.stats-section { - background: #ffffff; - padding: 24px 0; - border-bottom: 1px solid #e9ecef; +.search-input:focus { + border-color: rgba(255, 255, 255, 0.8); + background: white; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } -.stats-container { - max-width: 1200px; - margin: 0 auto; - padding: 0 24px; - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 20px; +/* Dark theme search input */ +[data-theme="dark"] .search-input { + color: #e6edf3; + background: rgba(33, 38, 45, 0.95); + border: 2px solid rgba(88, 166, 255, 0.3); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } -/* Compact Thread Stats Bar - Colorful Square Design */ -.thread-stats-bar { - background: rgba(255, 255, 255, 0.95); - padding: 12px 24px; +[data-theme="dark"] .search-input::placeholder { + color: #8b949e; +} + +[data-theme="dark"] .search-input:focus { + border-color: rgba(88, 166, 255, 0.6); + background: rgba(33, 38, 45, 1); + box-shadow: 0 4px 16px rgba(88, 166, 255, 0.2); +} + +.search-input.has-matches { + border-color: rgba(40, 167, 69, 0.8); + box-shadow: 0 4px 16px rgba(40, 167, 69, 0.2); +} + +.search-input.no-matches { + border-color: rgba(220, 53, 69, 0.8); + box-shadow: 0 4px 16px rgba(220, 53, 69, 0.2); +} + +.search-clear { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + width: 20px; + height: 20px; + padding: 0; + display: none; + align-items: center; + justify-content: center; + font-size: 14px; + line-height: 1; + color: #6c757d; + background: transparent; + border: none; + border-radius: 50%; + cursor: pointer; + transition: color var(--transition-fast); +} + +.search-clear:hover { + color: #2e3338; +} + +[data-theme="dark"] .search-clear { + color: #8b949e; +} + +[data-theme="dark"] .search-clear:hover { + color: #e6edf3; +} + +.search-wrapper.has-value .search-clear { + display: flex; +} + +/* Toolbar */ +.toolbar { + display: flex; + align-items: center; + gap: 6px; + margin-left: auto; +} + +.toolbar-btn { display: flex; align-items: center; justify-content: center; - gap: 16px; - font-size: 13px; - box-shadow: 0 2px 8px rgba(55, 118, 171, 0.2); + width: 32px; + height: 32px; + padding: 0; + font-size: 15px; + color: white; + background: rgba(255, 255, 255, 0.12); + border: 1px solid rgba(255, 255, 255, 0.18); + border-radius: 6px; + cursor: pointer; + transition: all var(--transition-fast); } -.thread-stat-item { - display: inline-flex; +.toolbar-btn:hover { + background: rgba(255, 255, 255, 0.22); + border-color: rgba(255, 255, 255, 0.35); +} + +.toolbar-btn:active { + transform: scale(0.95); +} + +/* -------------------------------------------------------------------------- + Sidebar + -------------------------------------------------------------------------- */ + +.sidebar { + width: var(--sidebar-width); + background: var(--bg-secondary); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + flex-shrink: 0; + overflow: hidden; + position: relative; +} + +.sidebar.collapsed { + width: var(--sidebar-collapsed) !important; + transition: width var(--transition-normal); +} + +.sidebar-toggle { + position: absolute; + top: 12px; + right: 10px; + width: 26px; + height: 26px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: 6px; + cursor: pointer; + transition: all var(--transition-fast); + z-index: 10; +} + +.sidebar-toggle svg { + transition: transform var(--transition-fast); +} + +.sidebar-toggle:hover { + color: var(--accent); + border-color: var(--accent); + background: var(--accent-glow); +} + +.sidebar.collapsed .sidebar-toggle { + right: 9px; +} + +.sidebar.collapsed .sidebar-toggle svg { + transform: rotate(180deg); +} + +.sidebar-content { + flex: 1; + overflow-y: auto; + padding: 44px 14px 14px; +} + +.sidebar.collapsed .sidebar-content { + display: none; +} + +.sidebar-resize-handle { + position: absolute; + top: 0; + right: 0; + width: 6px; + height: 100%; + cursor: col-resize; + background: transparent; + transition: background var(--transition-fast); + z-index: 11; +} + +.sidebar-resize-handle:hover, +.sidebar-resize-handle.resizing { + background: var(--python-gold); +} + +.sidebar-resize-handle::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 2px; + height: 40px; + background: var(--border); + border-radius: 1px; + opacity: 0; + transition: opacity var(--transition-fast); +} + +.sidebar-resize-handle:hover::before { + opacity: 1; +} + +.sidebar.collapsed .sidebar-resize-handle { + display: none; +} + +body.resizing-sidebar { + cursor: col-resize; + user-select: none; +} + +/* Sidebar Logo */ +.sidebar-logo { + display: flex; + justify-content: center; + margin-bottom: 16px; +} + +.sidebar-logo-img { + width: 90px; + height: 90px; + display: flex; + align-items: center; + justify-content: center; +} + +.sidebar-logo-img svg, +.sidebar-logo-img img { + width: 100%; + height: 100%; + object-fit: contain; +} + +/* Sidebar sections */ +.sidebar-section { + margin-bottom: 20px; +} + +.sidebar-section:last-child { + margin-bottom: 0; +} + +.section-title { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--accent); + margin: 0; + flex: 1; +} + +/* Collapsible sections */ +.collapsible .section-header { + display: flex; + align-items: center; + width: 100%; + padding: 0 0 8px 0; + margin-bottom: 10px; + background: none; + border: none; + border-bottom: 2px solid var(--python-gold); + cursor: pointer; + transition: all var(--transition-fast); +} + +.collapsible .section-header:hover { + opacity: 0.8; +} + +.section-chevron { + color: var(--text-muted); + transition: transform var(--transition-fast); +} + +.collapsible.collapsed .section-chevron { + transform: rotate(-90deg); +} + +.section-content { + overflow: hidden; + transition: max-height var(--transition-normal), opacity var(--transition-normal); + max-height: 1000px; + opacity: 1; +} + +.collapsible.collapsed .section-content { + max-height: 0; + opacity: 0; + margin-bottom: -10px; +} + +/* -------------------------------------------------------------------------- + Profile Summary Cards + -------------------------------------------------------------------------- */ + +.summary-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; +} + +.summary-card { + display: flex; align-items: center; gap: 8px; - background: white; - padding: 6px 14px; - border-radius: 4px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - transition: all 0.3s ease; - border: 2px solid; - min-width: 115px; - justify-content: center; - animation: fadeIn 0.5s ease-out backwards; + padding: 8px 10px; + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: 8px; + transition: all var(--transition-fast); + animation: slideUp 0.4s ease-out backwards; + animation-delay: calc(var(--i, 0) * 0.05s); + overflow: hidden; } -.thread-stat-item:nth-child(1) { animation-delay: 0s; } -.thread-stat-item:nth-child(3) { animation-delay: 0.1s; } -.thread-stat-item:nth-child(5) { animation-delay: 0.2s; } -.thread-stat-item:nth-child(7) { animation-delay: 0.3s; } +.summary-card:nth-child(1) { --i: 0; } +.summary-card:nth-child(2) { --i: 1; } +.summary-card:nth-child(3) { --i: 2; } +.summary-card:nth-child(4) { --i: 3; } + +.summary-card:hover { + border-color: var(--accent); + background: var(--accent-glow); +} + +.summary-icon { + font-size: 16px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-tertiary); + border-radius: 6px; + flex-shrink: 0; +} + +.summary-data { + min-width: 0; + flex: 1; + overflow: hidden; +} + +.summary-value { + font-family: var(--font-mono); + font-size: 13px; + font-weight: 700; + color: var(--accent); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.summary-label { + font-size: 8px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Efficiency Bar */ +.efficiency-section { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--border); +} + +.efficiency-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 5px; +} + +.efficiency-label { + font-size: 9px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.2px; +} + +.efficiency-value { + font-family: var(--font-mono); + font-size: 11px; + font-weight: 700; + color: var(--accent); +} + +.efficiency-bar { + height: 6px; + background: var(--bg-tertiary); + border-radius: 3px; + overflow: hidden; +} + +.efficiency-fill { + height: 100%; + background: linear-gradient(90deg, #28a745 0%, #20c997 50%, #17a2b8 100%); + border-radius: 3px; + transition: width 0.6s ease-out; + position: relative; + overflow: hidden; +} + +.efficiency-fill::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.4) 50%, + transparent 100% + ); + animation: shimmer 2s ease-in-out infinite; +} + +@keyframes shimmer { + 0% { left: -100%; } + 100% { left: 100%; } +} + +/* -------------------------------------------------------------------------- + Thread Stats Grid (in Sidebar) + -------------------------------------------------------------------------- */ + +.thread-stats-section { + display: block; +} + +.stats-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.stat-tile { + background: var(--bg-primary); + border-radius: 8px; + padding: 10px; + text-align: center; + border: 2px solid var(--border); + transition: all var(--transition-fast); + animation: fadeIn 0.4s ease-out backwards; + animation-delay: calc(var(--i, 0) * 0.05s); +} + +.stat-tile:nth-child(1) { --i: 0; } +.stat-tile:nth-child(2) { --i: 1; } +.stat-tile:nth-child(3) { --i: 2; } +.stat-tile:nth-child(4) { --i: 3; } + +.stat-tile:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-sm); +} + +.stat-tile-value { + font-family: var(--font-mono); + font-size: 16px; + font-weight: 700; + color: var(--text-primary); + line-height: 1.2; +} + +.stat-tile-label { + font-size: 9px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.3px; + color: var(--text-muted); + margin-top: 2px; +} + +/* Stat tile color variants */ +.stat-tile--green { --tile-color: 40, 167, 69; --tile-text: #28a745; } +.stat-tile--red { --tile-color: 220, 53, 69; --tile-text: #dc3545; } +.stat-tile--yellow { --tile-color: 255, 193, 7; --tile-text: #d39e00; } +.stat-tile--purple { --tile-color: 111, 66, 193; --tile-text: #6f42c1; } + +.stat-tile[class*="--"] { + border-color: rgba(var(--tile-color), 0.4); + background: linear-gradient(135deg, rgba(var(--tile-color), 0.08) 0%, var(--bg-primary) 100%); +} +.stat-tile[class*="--"] .stat-tile-value { color: var(--tile-text); } + +/* -------------------------------------------------------------------------- + Hotspot Cards + -------------------------------------------------------------------------- */ + +.hotspot { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 10px; + margin-bottom: 8px; + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: 8px; + cursor: pointer; + transition: all var(--transition-fast); + opacity: 0; + transform: translateY(8px); + box-shadow: var(--shadow-sm); +} + +.hotspot.visible { + opacity: 1; + transform: translateY(0); +} + +.hotspot:hover { + border-color: var(--accent); + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.hotspot.active { + border-color: var(--python-gold); + background: var(--accent-glow); + box-shadow: 0 0 0 3px var(--accent-glow); +} + +.hotspot:last-child { + margin-bottom: 0; +} + +.hotspot-rank { + width: 26px; + height: 26px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 12px; + flex-shrink: 0; + background: linear-gradient(135deg, var(--python-blue) 0%, var(--python-blue-light) 100%); + color: white; + box-shadow: 0 2px 4px rgba(55, 118, 171, 0.3); +} + +.hotspot-rank--1 { background: linear-gradient(135deg, #d4af37, #f4d03f); color: #5a4a00; } +.hotspot-rank--2 { background: linear-gradient(135deg, #a8a8a8, #c0c0c0); color: #4a4a4a; } +.hotspot-rank--3 { background: linear-gradient(135deg, #cd7f32, #e6a55a); color: #5a3d00; } + +.hotspot-info { + flex: 1; + min-width: 0; +} + +.hotspot-func { + font-family: var(--font-mono); + font-size: 11px; + font-weight: 600; + color: var(--text-primary); + line-height: 1.3; + word-break: break-word; + margin-bottom: 2px; +} + +.hotspot-file { + font-family: var(--font-mono); + font-size: 10px; + color: var(--text-muted); + margin-bottom: 3px; + word-break: break-all; +} + +.hotspot-stats { + font-family: var(--font-mono); + font-size: 10px; + color: var(--text-secondary); +} + +.hotspot-percent { + color: var(--accent); + font-weight: 600; +} + +/* -------------------------------------------------------------------------- + Legend + -------------------------------------------------------------------------- */ + +.legend-section { + margin-top: auto; + padding-top: 12px; +} + +.legend { + display: flex; + flex-direction: column; + gap: 4px; +} + +.legend-item { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 8px; + background: var(--bg-primary); + border-radius: 4px; + border: 1px solid var(--border-subtle); + font-size: 11px; +} + +.legend-color { + width: 20px; + height: 10px; + border-radius: 2px; + flex-shrink: 0; + border: 1px solid rgba(0, 0, 0, 0.08); +} + +.legend-label { + color: var(--text-primary); + font-weight: 500; + flex: 1; +} + +.legend-range { + font-family: var(--font-mono); + font-size: 9px; + color: var(--text-muted); +} + +/* -------------------------------------------------------------------------- + Thread Filter + -------------------------------------------------------------------------- */ + +.filter-section { + padding-top: 12px; + border-top: 1px solid var(--border); +} + +.filter-label { + display: block; + font-size: 10px; + font-weight: 600; + color: var(--text-muted); + margin-bottom: 6px; +} + +.filter-select { + width: 100%; + padding: 7px 28px 7px 10px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-primary); + background: var(--bg-primary); + border: 2px solid var(--accent); + border-radius: 6px; + cursor: pointer; + outline: none; + appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%233776ab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 6px center; + background-size: 14px; + transition: all var(--transition-fast); +} + +.filter-select:hover { + border-color: var(--accent-hover); + box-shadow: 0 2px 6px var(--accent-glow); +} + +.filter-select:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); +} + +/* -------------------------------------------------------------------------- + Chart Area + -------------------------------------------------------------------------- */ + +.chart-area { + flex: 1; + min-width: 0; + overflow: hidden; + background: var(--bg-primary); + position: relative; +} + +#chart { + width: 100%; + height: 100%; + padding: 16px; + overflow: auto; +} + +/* D3 Flamegraph overrides */ +.d3-flame-graph rect { + stroke: rgba(55, 118, 171, 0.3); + stroke-width: 1px; + cursor: pointer; + transition: filter 0.1s ease; +} + +.d3-flame-graph rect:hover { + stroke: var(--python-blue); + stroke-width: 2px; + filter: brightness(1.08); +} + +.d3-flame-graph text { + font-family: var(--font-sans); + font-size: 12px; + font-weight: 500; + fill: var(--text-primary); + pointer-events: none; +} + +/* Search highlight */ +.d3-flame-graph rect.search-match { + stroke: #ff6b35 !important; + stroke-width: 2px !important; + stroke-dasharray: 4 2; +} + +.d3-flame-graph rect.search-dim { + opacity: 0.25; +} + +/* -------------------------------------------------------------------------- + Status Bar + -------------------------------------------------------------------------- */ + +.status-bar { + height: var(--statusbar-height); + background: var(--bg-secondary); + border-top: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 16px; + gap: 16px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-secondary); + flex-shrink: 0; +} + +.status-item { + display: flex; + align-items: center; + gap: 5px; +} + +.status-item::before { + content: ''; + width: 4px; + height: 4px; + background: var(--python-gold); + border-radius: 50%; +} + +.status-item:first-child::before { + display: none; +} + +.status-label { + color: var(--text-muted); +} + +.status-value { + color: var(--text-primary); + font-weight: 500; +} + +.status-value.accent { + color: var(--accent); + font-weight: 600; +} + +/* -------------------------------------------------------------------------- + Tooltip + -------------------------------------------------------------------------- */ + +.python-tooltip { + position: absolute; + z-index: 1000; + pointer-events: none; + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: 8px; + padding: 14px; + max-width: 480px; + box-shadow: var(--shadow-lg); + font-family: var(--font-sans); + font-size: 13px; + color: var(--text-primary); + word-wrap: break-word; + overflow-wrap: break-word; + line-height: 1.5; +} + +.tooltip-header { + margin-bottom: 10px; +} + +.tooltip-title { + font-size: 14px; + font-weight: 600; + color: var(--accent); + line-height: 1.3; + word-break: break-word; + margin-bottom: 4px; +} + +.tooltip-location { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-muted); + background: var(--bg-tertiary); + padding: 4px 8px; + border-radius: 4px; + word-break: break-all; +} + +.tooltip-stats { + display: grid; + grid-template-columns: auto 1fr; + gap: 4px 14px; + font-size: 12px; +} + +.tooltip-stat-label { + color: var(--text-secondary); + font-weight: 500; +} + +.tooltip-stat-value { + color: var(--text-primary); + font-weight: 600; +} + +.tooltip-stat-value.accent { + color: var(--accent); +} + +.tooltip-source { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--border); +} + +.tooltip-source-title { + font-size: 11px; + font-weight: 600; + color: var(--accent); + margin-bottom: 6px; +} + +.tooltip-source-code { + font-family: var(--font-mono); + font-size: 10px; + line-height: 1.5; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px; + max-height: 140px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-all; +} + +.tooltip-source-line { + color: var(--text-secondary); + padding: 1px 0; +} + +.tooltip-source-line.current { + color: var(--accent); + font-weight: 600; +} + +.tooltip-hint { + margin-top: 10px; + padding-top: 8px; + border-top: 1px solid var(--border); + font-size: 11px; + color: var(--text-muted); + text-align: center; +} + +/* -------------------------------------------------------------------------- + Animations + -------------------------------------------------------------------------- */ @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } + from { opacity: 0; } + to { opacity: 1; } } @keyframes slideUp { from { opacity: 0; - transform: translateY(15px); + transform: translateY(12px); } to { opacity: 1; @@ -160,502 +1156,58 @@ @keyframes slideUp { } } -@keyframes gentlePulse { - 0%, 100% { box-shadow: 0 2px 8px rgba(55, 118, 171, 0.15); } - 50% { box-shadow: 0 2px 16px rgba(55, 118, 171, 0.4); } -} +/* -------------------------------------------------------------------------- + Focus States (Accessibility) + -------------------------------------------------------------------------- */ -/* Color-coded borders and subtle glow on hover */ -#gil-held-stat { - --stat-color: 40, 167, 69; - border-color: rgb(var(--stat-color)); - background: linear-gradient(135deg, rgba(var(--stat-color), 0.06) 0%, #ffffff 100%); -} - -#gil-released-stat { - --stat-color: 220, 53, 69; - border-color: rgb(var(--stat-color)); - background: linear-gradient(135deg, rgba(var(--stat-color), 0.06) 0%, #ffffff 100%); -} - -#gil-waiting-stat { - --stat-color: 255, 193, 7; - border-color: rgb(var(--stat-color)); - background: linear-gradient(135deg, rgba(var(--stat-color), 0.06) 0%, #ffffff 100%); -} - -#gc-stat { - --stat-color: 111, 66, 193; - border-color: rgb(var(--stat-color)); - background: linear-gradient(135deg, rgba(var(--stat-color), 0.06) 0%, #ffffff 100%); -} - -#gil-held-stat:hover, -#gil-released-stat:hover, -#gil-waiting-stat:hover, -#gc-stat:hover { - box-shadow: 0 0 12px rgba(var(--stat-color), 0.4), 0 1px 3px rgba(0, 0, 0, 0.08); -} - -.thread-stat-item .stat-label { - color: #5a6c7d; - font-weight: 600; - font-size: 11px; - letter-spacing: 0.3px; -} - -.thread-stat-item .stat-value { - color: #2e3338; - font-weight: 800; - font-size: 14px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; -} - -.thread-stat-separator { - color: rgba(0, 0, 0, 0.15); - font-weight: 300; - font-size: 16px; - position: relative; - z-index: 1; -} - -/* Responsive - stack on small screens */ -@media (max-width: 768px) { - .thread-stats-bar { - flex-wrap: wrap; - gap: 8px; - font-size: 11px; - padding: 10px 16px; - } - - .thread-stat-item { - padding: 4px 10px; - } - - .thread-stat-item .stat-label { - font-size: 11px; - } - - .thread-stat-item .stat-value { - font-size: 12px; - } - - .thread-stat-separator { - display: none; - } -} - -.stat-card { - background: #ffffff; - border: 1px solid #e9ecef; - border-radius: 12px; - padding: 20px; - display: flex; - align-items: flex-start; - gap: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - transition: all 0.2s ease; - min-height: 120px; - animation: slideUp 0.4s ease-out backwards; -} - -.stat-card:nth-child(1) { animation-delay: 0.1s; } -.stat-card:nth-child(2) { animation-delay: 0.2s; } -.stat-card:nth-child(3) { animation-delay: 0.3s; } - -.stat-card:hover { - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); - transform: translateY(-2px); -} - -.stat-icon { - font-size: 32px; - width: 56px; - height: 56px; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, #3776ab 0%, #4584bb 100%); - border-radius: 50%; - flex-shrink: 0; - box-shadow: 0 2px 8px rgba(55, 118, 171, 0.3); -} - -.stat-content { - flex: 1; -} - -.stat-label { - font-size: 14px; - color: #5a6c7d; - font-weight: 500; - margin-bottom: 4px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.stat-value { - font-size: 16px; - font-weight: 700; - color: #2e3338; - line-height: 1.3; - margin-bottom: 4px; - word-break: break-word; - overflow-wrap: break-word; -} - -.stat-file { - font-size: 12px; - color: #8b949e; - font-weight: 400; - margin-bottom: 2px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - word-break: break-word; - overflow-wrap: break-word; -} - -.stat-detail { - font-size: 12px; - color: #5a6c7d; - font-weight: 400; - line-height: 1.4; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - word-break: break-word; - overflow-wrap: break-word; -} - -.controls { - background: #f8f9fa; - border-bottom: 1px solid #e9ecef; - padding: 20px 0; - text-align: center; -} - -.controls-content { - max-width: 1200px; - margin: 0 auto; - padding: 0 24px; - text-align: center; -} - - -.controls button { - background: #3776ab; - color: white; - border: none; - padding: 12px 24px; - margin: 0 8px; - border-radius: 6px; - cursor: pointer; - font-weight: 600; - font-size: 14px; - font-family: inherit; - transition: all 0.2s ease; - box-shadow: 0 2px 4px rgba(55, 118, 171, 0.2); -} - -.controls button:hover { - background: #2d5aa0; - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(55, 118, 171, 0.3); -} - -.controls button:active { - transform: translateY(1px); - box-shadow: 0 1px 2px rgba(55, 118, 171, 0.2); -} - -.controls button.secondary { - background: #ffd43b; - color: #2e3338; -} - -.controls button.secondary:hover { - background: #ffcd02; -} - -.controls button.secondary:active { - background: #e6b800; -} - -.thread-filter-wrapper { - display: none; - align-items: center; - margin-left: 16px; - background: white; - border-radius: 6px; - padding: 4px 8px 4px 12px; - border: 2px solid #3776ab; - transition: all 0.2s ease; -} - -.thread-filter-wrapper:hover { - border-color: #2d5aa0; - box-shadow: 0 2px 6px rgba(55, 118, 171, 0.2); -} - -.thread-filter-label { - color: #3776ab; - font-size: 14px; - font-weight: 600; - margin-right: 8px; - display: flex; - align-items: center; -} - -.thread-filter-select { - background: transparent; - color: #2e3338; - border: none; - padding: 4px 24px 4px 4px; - font-size: 14px; - font-weight: 600; - cursor: pointer; - min-width: 120px; - font-family: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%233776ab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right 4px center; - background-size: 16px; -} - -.thread-filter-select:focus { - outline: none; -} - -.thread-filter-select:hover { - color: #3776ab; -} - -.thread-filter-select option { - padding: 8px; - background: white; - color: #2e3338; - font-weight: normal; -} - -#chart { - width: 100%; - height: calc(100vh - 160px); - overflow: hidden; - background: #ffffff; - padding: 0 40px; -} - -.d3-flame-graph rect { - /* Prefer selector specificity instead of !important */ - stroke: rgba(55, 118, 171, 0.3); - stroke-width: 1px; - cursor: pointer; - transition: all 0.1s ease; -} - -.d3-flame-graph rect:hover { - stroke: #3776ab; - stroke-width: 2px; - filter: brightness(1.05); -} - -.d3-flame-graph text { - /* Ensure labels use our font without !important */ - font-family: "Source Sans Pro", sans-serif; - font-size: 12px; - font-weight: 500; - fill: #2e3338; - pointer-events: none; -} - -.info-panel { - position: fixed; - bottom: 24px; - left: 84px; /* Leave space for the button */ - background: white; - padding: 24px; - border-radius: 8px; - border: 1px solid #e9ecef; - font-size: 14px; - max-width: 280px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - z-index: 1000; - display: none; -} - -.info-panel h3 { - margin: 0 0 16px 0; - color: #3776ab; - font-weight: 600; - font-size: 16px; - border-bottom: 2px solid #ffd43b; - padding-bottom: 8px; -} - -.info-panel p { - margin: 12px 0; - color: #5a6c7d; - line-height: 1.5; -} - -.info-panel strong { - color: #3776ab; -} - -#show-info-btn { - position: fixed; - bottom: 32px; - left: 32px; - z-index: 1100; - width: 44px; - height: 44px; - border-radius: 50%; - background: #3776ab; - color: white; - border: none; - font-size: 24px; - box-shadow: 0 2px 8px rgba(55, 118, 171, 0.15); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background 0.2s, transform 0.2s; - animation: gentlePulse 3s ease-in-out infinite; -} - -#show-info-btn:hover { - background: #2d5aa0; - animation: none; - transform: scale(1.05); -} - -#close-info-btn { - position: absolute; - top: 8px; - right: 12px; - background: none; - border: none; - font-size: 20px; - cursor: pointer; - color: #3776ab; -} - -@media (max-width: 600px) { - .python-logo { height: 48px; } - .python-logo img { height: 48px; } - #show-info-btn { - left: 8px; - bottom: 8px; - } - .info-panel { - left: 60px; /* Still leave space for button */ - bottom: 8px; - max-width: 90vw; - } -} - -.legend-panel { - position: fixed; - top: 24px; - left: 24px; - background: white; - padding: 24px; - border-radius: 8px; - border: 1px solid #e9ecef; - font-size: 14px; - max-width: 320px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - display: none; - z-index: 1001; -} - -.legend-panel h3 { - margin: 0 0 20px 0; - color: #3776ab; - font-weight: 600; - font-size: 18px; - text-align: center; - border-bottom: 2px solid #ffd43b; - padding-bottom: 8px; -} - -.legend-item { - display: flex; - align-items: center; - margin: 12px 0; - padding: 10px; - border-radius: 6px; - background: #f8f9fa; - border: 1px solid #e9ecef; -} - -.legend-color { - width: 28px; - height: 18px; - border-radius: 4px; - margin-right: 16px; - border: 1px solid rgba(0, 0, 0, 0.1); - flex-shrink: 0; -} - -.legend-label { - color: #2e3338; - font-weight: 600; - flex: 1; -} - -.legend-description { - color: #5a6c7d; - font-size: 12px; - margin-top: 2px; - font-weight: 400; -} - -.chart-container { - background: #ffffff; - margin: 0; - padding: 12px 0; -} - -/* Tooltip overflow fixes */ -.python-tooltip { - max-width: 500px !important; - word-wrap: break-word !important; - overflow-wrap: break-word !important; - box-sizing: border-box !important; -} - -/* Responsive tooltip adjustments */ -@media (max-width: 768px) { - .python-tooltip { - max-width: calc(100vw - 40px) !important; - max-height: calc(100vh - 80px) !important; - overflow-y: auto !important; - } -} - -@media (max-width: 480px) { - .python-tooltip { - max-width: calc(100vw - 20px) !important; - font-size: 12px !important; - } -} - -/* Accessibility: visible focus states */ button:focus-visible, select:focus-visible, input:focus-visible { - outline: 2px solid #ffd43b; + outline: 2px solid var(--python-gold); outline-offset: 2px; } -/* Smooth panel transitions */ -.legend-panel, -.info-panel { - transition: opacity 0.2s ease, transform 0.2s ease; +/* -------------------------------------------------------------------------- + Responsive + -------------------------------------------------------------------------- */ + +@media (max-width: 900px) { + .sidebar { + position: fixed; + left: 0; + top: var(--topbar-height); + bottom: var(--statusbar-height); + z-index: 100; + box-shadow: var(--shadow-lg); + } + + .sidebar.collapsed { + width: var(--sidebar-collapsed); + } + + .brand-subtitle { + display: none; + } + + .search-wrapper { + max-width: 220px; + } } -.legend-panel[style*="block"], -.info-panel[style*="block"] { - animation: slideUp 0.2s ease-out; +@media (max-width: 600px) { + .toolbar-btn:not(.theme-toggle) { + display: none; + } + + .search-wrapper { + max-width: 160px; + } + + .brand-info { + display: none; + } + + .stats-grid { + grid-template-columns: 1fr; + } } diff --git a/Lib/profiling/sampling/flamegraph.js b/Lib/profiling/sampling/flamegraph.js index 7faac0effbc..7a2b2ef2e31 100644 --- a/Lib/profiling/sampling/flamegraph.js +++ b/Lib/profiling/sampling/flamegraph.js @@ -5,93 +5,219 @@ let stringTable = []; let originalData = null; let currentThreadFilter = 'all'; -// Function to resolve string indices to actual strings +// Heat colors are now defined in CSS variables (--heat-1 through --heat-8) +// and automatically switch with theme changes - no JS color arrays needed! + +// ============================================================================ +// String Resolution +// ============================================================================ + function resolveString(index) { - if (typeof index === 'number' && index >= 0 && index < stringTable.length) { - return stringTable[index]; - } - // Fallback for non-indexed strings or invalid indices - return String(index); + if (index === null || index === undefined) { + return null; + } + if (typeof index === 'number' && index >= 0 && index < stringTable.length) { + return stringTable[index]; + } + return String(index); } -// Function to recursively resolve all string indices in flamegraph data function resolveStringIndices(node) { - if (!node) return node; + if (!node) return node; - // Create a copy to avoid mutating the original - const resolved = { ...node }; + const resolved = { ...node }; - // Resolve string fields - if (typeof resolved.name === 'number') { - resolved.name = resolveString(resolved.name); - } - if (typeof resolved.filename === 'number') { - resolved.filename = resolveString(resolved.filename); - } - if (typeof resolved.funcname === 'number') { - resolved.funcname = resolveString(resolved.funcname); - } + if (typeof resolved.name === 'number') { + resolved.name = resolveString(resolved.name); + } + if (typeof resolved.filename === 'number') { + resolved.filename = resolveString(resolved.filename); + } + if (typeof resolved.funcname === 'number') { + resolved.funcname = resolveString(resolved.funcname); + } - // Resolve source lines if present - if (Array.isArray(resolved.source)) { - resolved.source = resolved.source.map(index => - typeof index === 'number' ? resolveString(index) : index - ); - } + if (Array.isArray(resolved.source)) { + resolved.source = resolved.source.map(index => + typeof index === 'number' ? resolveString(index) : index + ); + } - // Recursively resolve children - if (Array.isArray(resolved.children)) { - resolved.children = resolved.children.map(child => resolveStringIndices(child)); - } + if (Array.isArray(resolved.children)) { + resolved.children = resolved.children.map(child => resolveStringIndices(child)); + } - return resolved; + return resolved; } -// Python color palette - cold to hot -const pythonColors = [ - "#fff4bf", // Coldest - light yellow (<1%) - "#ffec9e", // Cold - yellow (1-3%) - "#ffe47d", // Cool - golden yellow (3-6%) - "#ffdc5c", // Medium - golden (6-12%) - "#ffd43b", // Warm - Python gold (12-18%) - "#5592cc", // Hot - light blue (18-35%) - "#4584bb", // Very hot - medium blue (35-60%) - "#3776ab", // Hottest - Python blue (โฅ60%) -]; +// ============================================================================ +// Theme & UI Controls +// ============================================================================ -function ensureLibraryLoaded() { - if (typeof flamegraph === "undefined") { - console.error("d3-flame-graph library not loaded"); - document.getElementById("chart").innerHTML = - '
Click: Zoom into function
-Hover: Show detailed information
-Width: Time spent in function
-Height: Call stack depth
-Color: Performance intensity
-