@parca/profile 0.16.59 → 0.16.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/Callgraph/constants.d.ts +2 -0
  3. package/dist/Callgraph/constants.js +14 -0
  4. package/dist/Callgraph/index.d.ts +2 -2
  5. package/dist/Callgraph/index.js +106 -35
  6. package/dist/Callgraph/utils.d.ts +6 -6
  7. package/dist/Callgraph/utils.js +55 -29
  8. package/dist/GraphTooltip/index.d.ts +0 -1
  9. package/dist/IcicleGraph.d.ts +0 -1
  10. package/dist/MatchersInput/index.d.ts +0 -1
  11. package/dist/MetricsCircle/index.d.ts +0 -1
  12. package/dist/MetricsGraph/index.d.ts +0 -1
  13. package/dist/MetricsSeries/index.d.ts +0 -1
  14. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +0 -1
  15. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +0 -1
  16. package/dist/ProfileExplorer/index.d.ts +0 -1
  17. package/dist/ProfileIcicleGraph.d.ts +0 -1
  18. package/dist/ProfileMetricsGraph/index.d.ts +0 -1
  19. package/dist/ProfileSelector/CompareButton.d.ts +0 -1
  20. package/dist/ProfileSelector/MergeButton.d.ts +0 -1
  21. package/dist/ProfileSelector/index.d.ts +0 -1
  22. package/dist/ProfileSource.d.ts +0 -1
  23. package/dist/ProfileTypeSelector/index.d.ts +0 -1
  24. package/dist/ProfileView.d.ts +2 -3
  25. package/dist/ProfileView.js +15 -7
  26. package/dist/ProfileViewWithData.d.ts +0 -1
  27. package/dist/TopTable.d.ts +0 -1
  28. package/dist/components/DiffLegend.d.ts +0 -1
  29. package/dist/components/ProfileShareButton/ResultBox.d.ts +0 -1
  30. package/dist/components/ProfileShareButton/index.d.ts +0 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/styles.css +1 -1
  33. package/package.json +7 -6
  34. package/src/Callgraph/constants.ts +15 -0
  35. package/src/Callgraph/index.tsx +232 -66
  36. package/src/Callgraph/utils.ts +68 -39
  37. package/src/ProfileView.tsx +128 -114
  38. package/dist/Callgraph/Edge/index.d.ts +0 -23
  39. package/dist/Callgraph/Edge/index.js +0 -30
  40. package/dist/Callgraph/Node/index.d.ts +0 -20
  41. package/dist/Callgraph/Node/index.js +0 -37
  42. package/src/Callgraph/Edge/index.tsx +0 -59
  43. package/src/Callgraph/Node/index.tsx +0 -66
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- /*! tailwindcss v3.2.1 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-0{margin:0}.m-2{margin:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mr-3{margin-right:.75rem}.ml-auto{margin-left:auto}.ml-2{margin-left:.5rem}.mr-6{margin-right:1.5rem}.mt-2{margin-top:.5rem}.mt-1{margin-top:.25rem}.mb-2{margin-bottom:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.h-36{height:9rem}.h-1{height:.25rem}.h-4{height:1rem}.max-h-\[400px\]{max-height:400px}.w-full{width:100%}.w-2\/5{width:40%}.w-auto{width:auto}.w-1\/2{width:50%}.w-\[150px\]{width:150px}.w-1\/5{width:20%}.w-4\/5{width:80%}.w-fit{width:-moz-fit-content;width:fit-content}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-40{width:10rem}.w-8{width:2rem}.w-16{width:4rem}.w-\[420px\]{width:420px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.cursor-default{cursor:default}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-scroll{overflow-x:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded{border-radius:.25rem}.rounded-none{border-radius:0}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.rounded-tr-none{border-top-right-radius:0}.rounded-br-none{border-bottom-right-radius:0}.rounded-tl-none{border-top-left-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-width:1px}.border-l-0{border-left-width:0}.border-r-0{border-right-width:0}.border-b{border-bottom-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.fill-\[\#161616\]{fill:#161616}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.p-10{padding:2.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-20{padding-bottom:5rem;padding-top:5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-2{padding-right:.5rem}.pb-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-90{opacity:.9}.opacity-100{opacity:1}.opacity-0{opacity:0}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:bg-\[\#62626212\]:hover{background-color:#62626212}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-indigo-800:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(55 48 163/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:fill-\[\#ffffff\]{fill:#fff}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:hover\:bg-\[\#ffffff12\]:hover{background-color:#ffffff12}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
1
+ /*! tailwindcss v3.2.1 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-0{margin:0}.m-2{margin:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mr-3{margin-right:.75rem}.ml-auto{margin-left:auto}.ml-2{margin-left:.5rem}.mr-6{margin-right:1.5rem}.mt-2{margin-top:.5rem}.mt-1{margin-top:.25rem}.mb-2{margin-bottom:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.h-36{height:9rem}.h-1{height:.25rem}.h-4{height:1rem}.max-h-\[400px\]{max-height:400px}.w-full{width:100%}.w-2\/5{width:40%}.w-auto{width:auto}.w-1\/2{width:50%}.w-\[150px\]{width:150px}.w-1\/5{width:20%}.w-4\/5{width:80%}.w-fit{width:-moz-fit-content;width:fit-content}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-40{width:10rem}.w-8{width:2rem}.w-16{width:4rem}.w-\[420px\]{width:420px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.cursor-default{cursor:default}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-scroll{overflow-x:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded-lg{border-radius:.5rem}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.rounded-tr-none{border-top-right-radius:0}.rounded-br-none{border-bottom-right-radius:0}.rounded-tl-none{border-top-left-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-width:1px}.border-l-0{border-left-width:0}.border-r-0{border-right-width:0}.border-b{border-bottom-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.fill-\[\#161616\]{fill:#161616}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.p-10{padding:2.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-20{padding-bottom:5rem;padding-top:5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-2{padding-right:.5rem}.pb-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-90{opacity:.9}.opacity-100{opacity:1}.opacity-0{opacity:0}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:bg-\[\#62626212\]:hover{background-color:#62626212}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-indigo-800:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(55 48 163/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:fill-\[\#ffffff\]{fill:#fff}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:hover\:bg-\[\#ffffff12\]:hover{background-color:#ffffff12}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.59",
3
+ "version": "0.16.60",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@iconify/react": "^3.2.2",
7
7
  "@parca/client": "^0.16.53",
8
- "@parca/components": "^0.16.55",
8
+ "@parca/components": "^0.16.56",
9
9
  "@parca/dynamicsize": "^0.16.51",
10
10
  "@parca/functions": "^0.16.51",
11
11
  "@parca/parser": "^0.16.49",
@@ -20,10 +20,11 @@
20
20
  "react-konva": "18.2.3"
21
21
  },
22
22
  "devDependencies": {
23
- "@types/cytoscape": "3.19.9",
24
23
  "@types/d3": "7.4.0",
25
- "@types/react-copy-to-clipboard": "5.0.4",
26
- "@types/react-cytoscapejs": "1.2.2"
24
+ "@types/react-copy-to-clipboard": "5.0.4"
25
+ },
26
+ "resolutions": {
27
+ "konva": "8.3.13"
27
28
  },
28
29
  "main": "dist/index.js",
29
30
  "scripts": {
@@ -39,5 +40,5 @@
39
40
  "access": "public",
40
41
  "registry": "https://registry.npmjs.org/"
41
42
  },
42
- "gitHead": "d1389b648874097c3eeb9143c94469847fedede2"
43
+ "gitHead": "6b4746c5144bd8473a7f2f2bfc44963df8eacb3e"
43
44
  }
@@ -0,0 +1,15 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ export const DEFAULT_NODE_HEIGHT = 20;
15
+ export const GRAPH_MARGIN = 15;
@@ -14,50 +14,182 @@
14
14
  import {useState, useEffect, useRef} from 'react';
15
15
  import graphviz from 'graphviz-wasm';
16
16
  import * as d3 from 'd3';
17
- import {Stage, Layer} from 'react-konva';
18
- import Tooltip from '../GraphTooltip';
17
+ import {Stage, Layer, Rect, Arrow, Text, Label} from 'react-konva';
18
+ import {KonvaEventObject} from 'konva/lib/Node';
19
19
  import {CallgraphNode, CallgraphEdge, Callgraph as CallgraphType} from '@parca/client';
20
- import {jsonToDot} from './utils';
21
- import Node, {INode} from './Node';
22
- import Edge, {IEdge} from './Edge';
20
+ import {jsonToDot, getCurvePoints} from './utils';
23
21
  import type {HoveringNode} from '../GraphTooltip';
22
+ import {useAppSelector, selectSearchNodeString} from '@parca/store';
23
+ import {isSearchMatch} from '@parca/functions';
24
+ import Tooltip from '../GraphTooltip';
25
+ import {DEFAULT_NODE_HEIGHT, GRAPH_MARGIN} from './constants';
24
26
 
27
+ interface NodeProps {
28
+ node: INode;
29
+ hoveredNode: INode | null;
30
+ setHoveredNode: (node: INode | null) => void;
31
+ isCurrentSearchMatch: boolean;
32
+ }
33
+ interface EdgeProps {
34
+ edge: GraphvizEdge;
35
+ sourceNode: {x: number; y: number};
36
+ targetNode: {x: number; y: number};
37
+ xScale: (x: number) => number;
38
+ yScale: (y: number) => number;
39
+ isCurrentSearchMatch: boolean;
40
+ }
25
41
  export interface Props {
26
42
  graph: CallgraphType;
27
43
  sampleUnit: string;
28
44
  width: number;
45
+ colorRange: [string, string];
29
46
  }
30
47
 
31
- interface graphvizObject extends CallgraphNode {
48
+ interface GraphvizNode extends CallgraphNode {
32
49
  _gvid: number;
33
50
  name: string;
34
51
  pos: string;
52
+ functionName: string;
53
+ color: string;
54
+ width: string | number;
55
+ height: string | number;
35
56
  }
36
57
 
37
- interface graphvizEdge extends CallgraphEdge {
58
+ interface INode extends GraphvizNode {
59
+ x: number;
60
+ y: number;
61
+ data: {id: string};
62
+ mouseX?: number;
63
+ mouseY?: number;
64
+ }
65
+
66
+ interface GraphvizEdge extends CallgraphEdge {
38
67
  _gvid: number;
39
68
  tail: number;
40
69
  head: number;
41
70
  pos: string;
71
+ color: string;
72
+ opacity: string;
73
+ boxHeight: number;
42
74
  }
43
75
 
44
- interface graphvizType {
45
- edges: graphvizEdge[];
46
- objects: graphvizObject[];
76
+ interface GraphvizType {
77
+ edges: GraphvizEdge[];
78
+ objects: GraphvizNode[];
47
79
  bb: string;
48
80
  }
49
81
 
50
- const Callgraph = ({graph, sampleUnit, width}: Props): JSX.Element => {
82
+ const Node = ({
83
+ node,
84
+ hoveredNode,
85
+ setHoveredNode,
86
+ isCurrentSearchMatch,
87
+ }: NodeProps): JSX.Element => {
88
+ const {
89
+ data: {id},
90
+ x,
91
+ y,
92
+ color,
93
+ functionName,
94
+ width: widthString,
95
+ height: heightString,
96
+ } = node;
97
+ const isHovered = Boolean(hoveredNode) && hoveredNode?.data.id === id;
98
+ const width = Number(widthString);
99
+ const height = Number(heightString);
100
+ const textPadding = 6;
101
+ const opacity = isCurrentSearchMatch ? 1 : 0.1;
102
+
103
+ return (
104
+ <Label x={x - width / 2} y={y - height / 2}>
105
+ <Rect
106
+ width={width}
107
+ height={height}
108
+ fill={color}
109
+ opacity={opacity}
110
+ cornerRadius={3}
111
+ stroke={isHovered ? 'black' : color}
112
+ strokeWidth={2}
113
+ onMouseOver={e => {
114
+ setHoveredNode({...node, mouseX: e.evt.clientX, mouseY: e.evt.clientY});
115
+ }}
116
+ onMouseOut={() => {
117
+ setHoveredNode(null);
118
+ }}
119
+ />
120
+ {width > DEFAULT_NODE_HEIGHT + 10 && (
121
+ <Text
122
+ text={functionName}
123
+ fontSize={10}
124
+ fill="white"
125
+ width={width - textPadding}
126
+ height={height - textPadding}
127
+ x={textPadding / 2}
128
+ y={textPadding / 2}
129
+ align="center"
130
+ verticalAlign="middle"
131
+ listening={false}
132
+ />
133
+ )}
134
+ </Label>
135
+ );
136
+ };
137
+
138
+ const Edge = ({
139
+ edge,
140
+ sourceNode,
141
+ targetNode,
142
+ xScale,
143
+ yScale,
144
+ isCurrentSearchMatch,
145
+ }: EdgeProps): JSX.Element => {
146
+ const {pos, color, head, tail, opacity, boxHeight} = edge;
147
+
148
+ const points = getCurvePoints({
149
+ pos,
150
+ xScale,
151
+ yScale,
152
+ source: [sourceNode.x, sourceNode.y],
153
+ target: [targetNode.x, targetNode.y],
154
+ offset: boxHeight / 2,
155
+ isSelfLoop: head === tail,
156
+ });
157
+
158
+ return (
159
+ <Arrow
160
+ points={points}
161
+ bezier={true}
162
+ stroke={color}
163
+ strokeWidth={3}
164
+ pointerLength={10}
165
+ pointerWidth={10}
166
+ fill={color}
167
+ opacity={isCurrentSearchMatch ? Number(opacity) : 0}
168
+ />
169
+ );
170
+ };
171
+
172
+ const Callgraph = ({graph, sampleUnit, width, colorRange}: Props): JSX.Element => {
51
173
  const containerRef = useRef<HTMLDivElement>(null);
52
- const [graphData, setGraphData] = useState<string | null>(null);
174
+ const [graphData, setGraphData] = useState<any>(null);
53
175
  const [hoveredNode, setHoveredNode] = useState<INode | null>(null);
176
+ const [stage, setStage] = useState<{scale: {x: number; y: number}; x: number; y: number}>({
177
+ scale: {x: 1, y: 1},
178
+ x: 0,
179
+ y: 0,
180
+ });
54
181
  const {nodes: rawNodes, cumulative: total} = graph;
55
- const nodeRadius = 12;
182
+ const currentSearchString = useAppSelector(selectSearchNodeString);
183
+ const isSearchEmpty = currentSearchString === undefined;
56
184
 
57
185
  useEffect(() => {
58
186
  const getDataWithPositions = async (): Promise<void> => {
59
187
  // 1. Translate JSON to 'dot' graph string
60
- const dataAsDot = jsonToDot({graph, width, nodeRadius});
188
+ const dataAsDot = jsonToDot({
189
+ graph,
190
+ width,
191
+ colorRange,
192
+ });
61
193
 
62
194
  // 2. Use Graphviz-WASM to translate the 'dot' graph to a 'JSON' graph
63
195
  await graphviz.loadWASM(); // need to load the WASM instance and wait for it
@@ -70,86 +202,120 @@ const Callgraph = ({graph, sampleUnit, width}: Props): JSX.Element => {
70
202
  if (width !== null) {
71
203
  void getDataWithPositions();
72
204
  }
73
- }, [graph, width]);
205
+ }, [graph, width, colorRange]);
74
206
 
75
207
  // 3. Render the graph with calculated layout in Canvas container
76
208
  if (width == null || graphData == null) return <></>;
209
+ const {objects: gvizNodes, edges, bb: boundingBox} = JSON.parse(graphData) as GraphvizType;
77
210
 
78
- const height = width;
79
- const {objects, edges: gvizEdges, bb: boundingBox} = JSON.parse(graphData) as graphvizType;
80
-
81
- const cumulatives: string[] = objects
82
- .filter(node => node !== undefined)
83
- .map(node => node.cumulative);
84
- if (cumulatives.length === 0) {
85
- cumulatives.push('0');
86
- }
87
-
88
- const valueRange = (d3.extent(cumulatives) as [string, string]).map(value => parseInt(value));
89
-
90
- const colorScale = d3
91
- .scaleSequentialLog(d3.interpolateRdGy)
92
- .domain(valueRange)
93
- .range(['lightgrey', 'red']);
94
211
  const graphBB = boundingBox.split(',');
212
+ const bbWidth = Number(graphBB[2]);
213
+ const bbHeight = Number(graphBB[3]);
214
+ const height = (width * bbHeight) / bbWidth;
95
215
  const xScale = d3
96
216
  .scaleLinear()
97
- .domain([0, Number(graphBB[2])])
98
- .range([0, width]);
217
+ .domain([0, bbWidth])
218
+ .range([0, width - 2 * GRAPH_MARGIN]);
99
219
  const yScale = d3
100
220
  .scaleLinear()
101
- .domain([0, Number(graphBB[3])])
102
- .range([0, height]);
221
+ .domain([0, bbHeight])
222
+ .range([0, height - 2 * GRAPH_MARGIN]);
103
223
 
104
- const nodes: INode[] = objects.map(object => {
105
- const pos = object.pos.split(',');
224
+ const nodes: INode[] = gvizNodes.map((node: GraphvizNode) => {
225
+ const [x, y] = node.pos.split(',');
106
226
  return {
107
- ...object,
108
- id: object._gvid,
109
- x: xScale(parseInt(pos[0])),
110
- y: yScale(parseInt(pos[1])),
111
- color: colorScale(Number(object.cumulative)),
112
- data: rawNodes.find(n => n.id === object.name) ?? {id: 'n0'},
227
+ ...node,
228
+ x: xScale(Number(x)),
229
+ y: yScale(Number(y)),
230
+ data: rawNodes.find(n => n.id === node.name) ?? {id: 'n0'},
113
231
  };
114
232
  });
115
233
 
116
- const edges: IEdge[] = gvizEdges.map(edge => ({
117
- ...edge,
118
- source: edge.head,
119
- target: edge.tail,
120
- points: edge.pos,
121
- color: colorScale(+edge.cumulative),
122
- }));
234
+ // 4. Add zooming
235
+ const handleWheel: (e: KonvaEventObject<WheelEvent>) => void = e => {
236
+ e.evt.preventDefault();
237
+
238
+ const scaleXBy = 0.95;
239
+ const scaleYBy = 1.05;
240
+ const stage = e.target.getStage();
241
+
242
+ if (stage !== null) {
243
+ const oldScale = stage.scaleX();
244
+ const {x, y} = stage.getPointerPosition() ?? {x: 0, y: 0};
245
+ const mousePointTo = {
246
+ x: x / oldScale - stage.x() / oldScale,
247
+ y: y / oldScale - stage.y() / oldScale,
248
+ };
249
+
250
+ const newXScale = e.evt.deltaX > 0 ? oldScale * scaleXBy : oldScale / scaleXBy;
251
+ const newYScale = e.evt.deltaY > 0 ? oldScale * scaleYBy : oldScale / scaleYBy;
252
+
253
+ stage.scale({x: newXScale, y: newYScale});
254
+
255
+ setStage({
256
+ scale: {x: newXScale, y: newYScale},
257
+ x: -(mousePointTo.x - x / newXScale) * newXScale,
258
+ y: -(mousePointTo.y - y / newYScale) * newYScale,
259
+ });
260
+ }
261
+ };
123
262
 
124
263
  return (
125
264
  <div className="relative">
126
265
  <div className={`w-[${width}px] h-[${height}px]`} ref={containerRef}>
127
- <Stage width={width} height={height}>
128
- <Layer>
129
- {edges.map(edge => {
130
- const sourceNode = nodes.find(n => n.id === edge.source) ?? {x: 0, y: 0};
131
- const targetNode = nodes.find(n => n.id === edge.target) ?? {x: 0, y: 0};
266
+ <Stage
267
+ width={width}
268
+ height={height}
269
+ onWheel={handleWheel}
270
+ scaleX={stage.scale.x}
271
+ scaleY={stage.scale.y}
272
+ x={stage.x}
273
+ y={stage.y}
274
+ draggable
275
+ >
276
+ <Layer offsetX={-GRAPH_MARGIN} offsetY={-GRAPH_MARGIN}>
277
+ {edges.map((edge: GraphvizEdge) => {
278
+ // 'tail' in graphviz-wasm means 'source' and 'head' means 'target'
279
+ const sourceNode = nodes.find(n => n._gvid === edge.tail) ?? {
280
+ x: 0,
281
+ y: 0,
282
+ functionName: '',
283
+ };
284
+ const targetNode = nodes.find(n => n._gvid === edge.head) ?? {
285
+ x: 0,
286
+ y: 0,
287
+ functionName: '',
288
+ };
289
+ const isCurrentSearchMatch = isSearchEmpty
290
+ ? true
291
+ : isSearchMatch(currentSearchString, sourceNode.functionName) &&
292
+ isSearchMatch(currentSearchString, targetNode.functionName);
132
293
  return (
133
294
  <Edge
134
- key={`edge-${edge.source}-${edge.target}`}
295
+ key={`edge-${edge.tail}-${edge.head}`}
135
296
  edge={edge}
136
297
  xScale={xScale}
137
298
  yScale={yScale}
138
299
  sourceNode={sourceNode}
139
300
  targetNode={targetNode}
140
- nodeRadius={nodeRadius}
301
+ isCurrentSearchMatch={isCurrentSearchMatch}
302
+ />
303
+ );
304
+ })}
305
+ {nodes.map(node => {
306
+ const isCurrentSearchMatch = isSearchEmpty
307
+ ? true
308
+ : isSearchMatch(currentSearchString, node.functionName);
309
+ return (
310
+ <Node
311
+ key={`node-${node._gvid}`}
312
+ node={node}
313
+ hoveredNode={hoveredNode}
314
+ setHoveredNode={setHoveredNode}
315
+ isCurrentSearchMatch={isCurrentSearchMatch}
141
316
  />
142
317
  );
143
318
  })}
144
- {nodes.map(node => (
145
- <Node
146
- key={`node-${node.data.id}`}
147
- node={node}
148
- hoveredNode={hoveredNode}
149
- setHoveredNode={setHoveredNode}
150
- nodeRadius={nodeRadius}
151
- />
152
- ))}
153
319
  </Layer>
154
320
  </Stage>
155
321
  <Tooltip
@@ -11,77 +11,99 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ import * as d3 from 'd3';
14
15
  import {CallgraphNode, CallgraphEdge} from '@parca/client';
16
+ import {DEFAULT_NODE_HEIGHT} from './constants';
15
17
 
16
18
  export const pixelsToInches = (pixels: number): number => pixels / 96;
17
19
 
18
- export const parseEdgePos = ({
20
+ export const getCurvePoints = ({
19
21
  pos,
20
22
  xScale = n => n,
21
23
  yScale = n => n,
22
24
  source = [],
23
25
  target = [],
24
- nodeRadius,
26
+ offset = 0,
25
27
  isSelfLoop = false,
26
28
  }: {
27
29
  pos: string;
28
- xScale?: (pos: number) => void;
29
- yScale?: (pos: number) => void;
30
+ xScale?: (pos: number) => number;
31
+ yScale?: (pos: number) => number;
30
32
  source?: number[];
31
33
  target?: number[];
32
- nodeRadius: number;
33
34
  isSelfLoop?: boolean;
35
+ offset?: number;
34
36
  }): number[] => {
35
- const parts = pos.split(' ');
36
- const arrow = parts.shift() ?? '';
37
- const partsAsArrays = parts.map(part => part.split(','));
38
- const scalePosArray = (posArr: string[]): number[] => [+xScale(+posArr[0]), +yScale(+posArr[1])];
39
- const [_start, cp1, cp2, _end] = partsAsArrays.map(posArr => scalePosArray(posArr));
40
- const arrowEnd: number[] = scalePosArray(arrow.replace('e,', '').split(','));
41
-
42
- const getTargetWithOffset = (target: number[], lastEdgePoint: number[]): number[] => {
43
- const diffX = target[0] - lastEdgePoint[0];
44
- const diffY = target[1] - lastEdgePoint[1];
45
- const diffZ = Math.hypot(diffX, diffY);
46
-
47
- const offsetX = (diffX * nodeRadius) / diffZ;
48
- const offsetY = (diffY * nodeRadius) / diffZ;
49
-
50
- return [target[0] - offsetX, target[1] - offsetY];
51
- };
52
-
53
37
  if (isSelfLoop) {
54
38
  const [sourceX, sourceY] = source;
55
39
  const [targetX, targetY] = target;
40
+
56
41
  return [
57
42
  sourceX,
58
- sourceY + nodeRadius,
43
+ sourceY + offset,
59
44
  sourceX,
60
- sourceY + 3 * nodeRadius,
61
- targetX + 5 * nodeRadius,
45
+ sourceY + 3 * offset,
46
+ targetX + 5 * offset,
62
47
  targetY,
63
- targetX + nodeRadius,
48
+ targetX + offset,
64
49
  targetY,
65
50
  ];
66
51
  }
67
- return [...source, ...cp1, ...cp2, ...getTargetWithOffset(target, arrowEnd)];
52
+
53
+ // graphviz pos format is in format 'endpoint,startpoint,triple(cp1,cp2,end),...triple...'
54
+ const scalePoint = (point: number[]): number[] => [xScale(point[0]), yScale(point[1])];
55
+ const strAsNumArray = (string: string): number[] =>
56
+ string
57
+ .replace('e,', '')
58
+ .split(',')
59
+ .map(str => Number(str));
60
+ const getLastPointWithOffset = (target: number[], last: number[], offset: number): number[] => {
61
+ const [targetX, targetY] = target;
62
+ const [lastX, lastY] = last;
63
+ const diffX = targetX - lastX;
64
+ const diffY = targetY - lastY;
65
+ const diffZ = Math.hypot(diffX, diffY);
66
+
67
+ const offsetX = (diffX * offset) / diffZ;
68
+ const offsetY = (diffY * offset) / diffZ;
69
+
70
+ return [targetX - offsetX, targetY - offsetY];
71
+ };
72
+ const points: number[][] = pos.split(' ').map(str => strAsNumArray(str));
73
+ const scaledPoints: number[][] = points.map(point => scalePoint(point));
74
+
75
+ const lastPointIndex = scaledPoints.length - 1;
76
+ const lastPointWithOffset = getLastPointWithOffset(target, scaledPoints[lastPointIndex], offset);
77
+
78
+ return [source, ...scaledPoints.slice(2, points.length - 1), lastPointWithOffset].flat();
68
79
  };
69
80
 
81
+ const objectAsDotAttributes = (obj: {[key: string]: string | number}): string =>
82
+ Object.entries(obj)
83
+ .map(entry => `${entry[0]}="${entry[1]}"`)
84
+ .join(' ');
85
+
70
86
  export const jsonToDot = ({
71
87
  graph,
72
- width,
73
- nodeRadius,
88
+ colorRange,
74
89
  }: {
75
90
  graph: {nodes: CallgraphNode[]; edges: CallgraphEdge[]};
76
91
  width: number;
77
- nodeRadius: number;
92
+ colorRange: [string, string];
78
93
  }): string => {
79
94
  const {nodes, edges} = graph;
95
+ const cumulatives = nodes.map((node: CallgraphNode) => node.cumulative);
96
+ const cumulativesRange = d3.extent(cumulatives).map(value => Number(value));
80
97
 
81
- const objectAsDotAttributes = (obj: {[key: string]: string}): string =>
82
- Object.entries(obj)
83
- .map(entry => `${entry[0]}="${entry[1]}"`)
84
- .join(' ');
98
+ const colorScale = d3
99
+ .scaleSequentialLog(d3.interpolateBlues)
100
+ .domain(cumulativesRange)
101
+ .range(colorRange);
102
+ const colorOpacityScale = d3.scaleSequentialLog().domain(cumulativesRange).range([0.2, 1]);
103
+ const boxWidthScale = d3
104
+ .scaleLog()
105
+ .domain(cumulativesRange)
106
+ .range([DEFAULT_NODE_HEIGHT, DEFAULT_NODE_HEIGHT + 40]);
85
107
 
86
108
  const nodesAsStrings = nodes.map((node: CallgraphNode) => {
87
109
  const dataAttributes = {
@@ -89,6 +111,9 @@ export const jsonToDot = ({
89
111
  functionName: node.meta?.function?.name ?? '',
90
112
  cumulative: node.cumulative ?? '',
91
113
  root: (node.id === 'root').toString(),
114
+ // TODO: set box width scale to be based on flat value once we have that value available
115
+ width: boxWidthScale(Number(node.cumulative)),
116
+ color: colorScale(Number(node.cumulative)),
92
117
  };
93
118
 
94
119
  return `"${node.id}" [${objectAsDotAttributes(dataAttributes)}]`;
@@ -97,17 +122,21 @@ export const jsonToDot = ({
97
122
  const edgesAsStrings = edges.map((edge: CallgraphEdge) => {
98
123
  const dataAttributes = {
99
124
  cumulative: edge.cumulative,
125
+ color: colorRange[1],
126
+ opacity: colorOpacityScale(Number(edge.cumulative)),
127
+ boxHeight: DEFAULT_NODE_HEIGHT,
100
128
  };
129
+
101
130
  return `"${edge.source}" -> "${edge.target}" [${objectAsDotAttributes(dataAttributes)}]`;
102
131
  });
103
132
 
104
133
  const graphAsDot = `digraph "callgraph" {
105
- rankdir="TB"
134
+ rankdir="BT"
135
+ overlap="prism"
106
136
  ratio="1,3"
107
- size="${pixelsToInches(width)}, ${pixelsToInches(width)}!"
108
- margin=10
137
+ margin=15
109
138
  edge [margin=0]
110
- node [margin=0 width=${nodeRadius}]
139
+ node [shape=box style=rounded height=${DEFAULT_NODE_HEIGHT}]
111
140
  ${nodesAsStrings.join(' ')}
112
141
  ${edgesAsStrings.join(' ')}
113
142
  }`;