From 1ac040202865eb389250c47c07cd764e58b970a7 Mon Sep 17 00:00:00 2001 From: rlazarini <remi.lazarini@cea.fr> Date: Thu, 11 Apr 2024 11:08:56 +0200 Subject: [PATCH] [ivette] added module flamegraph --- ivette/package.json | 1 + .../src/dome/renderer/controls/gallery.json | 6 + ivette/src/frama-c/plugins/eva/Flamegraph.tsx | 172 ++++++++++++++++++ ivette/src/frama-c/plugins/eva/index.tsx | 4 +- ivette/src/frama-c/plugins/eva/style.css | 11 ++ ivette/src/frama-c/react-flame-graph.d.ts | 23 +++ ivette/yarn.lock | 32 ++++ 7 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 ivette/src/frama-c/plugins/eva/Flamegraph.tsx create mode 100644 ivette/src/frama-c/react-flame-graph.d.ts diff --git a/ivette/package.json b/ivette/package.json index e44cabd9e3d..a443b467f10 100644 --- a/ivette/package.json +++ b/ivette/package.json @@ -48,6 +48,7 @@ "react-dom": "^18", "react-draggable": "^4.4.6", "react-fast-compare": "^3.2.2", + "react-flame-graph" : "^1.4.0", "react-force-graph-2d": "^1.25.4", "react-force-graph-3d": "^1.24.2", "react-infinite-scroller": "^1.2.6", diff --git a/ivette/src/dome/renderer/controls/gallery.json b/ivette/src/dome/renderer/controls/gallery.json index 36bc7aa8000..d5d97ee59f9 100644 --- a/ivette/src/dome/renderer/controls/gallery.json +++ b/ivette/src/dome/renderer/controls/gallery.json @@ -316,6 +316,12 @@ "viewBox": "0 0 24 24", "path": "M15.428 18.429v-2.143q0-0.188-0.121-0.308t-0.308-0.121h-1.286v-6.857q0-0.188-0.121-0.308t-0.308-0.121h-4.286q-0.188 0-0.308 0.121t-0.121 0.308v2.143q0 0.188 0.121 0.308t0.308 0.121h1.286v4.286h-1.286q-0.188 0-0.308 0.121t-0.121 0.308v2.143q0 0.188 0.121 0.308t0.308 0.121h6q0.188 0 0.308-0.121t0.121-0.308zM13.714 6.429v-2.143q0-0.188-0.121-0.308t-0.308-0.121h-2.571q-0.188 0-0.308 0.121t-0.121 0.308v2.143q0 0.188 0.121 0.308t0.308 0.121h2.571q0.188 0 0.308-0.121t0.121-0.308zM22.285 12q0 2.799-1.379 5.163t-3.743 3.743-5.163 1.379-5.163-1.379-3.743-3.743-1.379-5.163 1.379-5.163 3.743-3.743 5.163-1.379 5.163 1.379 3.743 3.743 1.379 5.163z" }, + "TARGET": { + "section": "Buttons", + "title": "Target", + "viewBox": "0 0 32 32", + "path": "M32 14h-3.154c-0.864-5.57-5.276-9.982-10.846-10.846v-3.154h-4v3.154c-5.57 0.864-9.982 5.276-10.846 10.846h-3.154v4h3.154c0.864 5.57 5.276 9.982 10.846 10.846v3.154h4v-3.154c5.57-0.864 9.982-5.276 10.846-10.846h3.154v-4zM24.776 14h-3.118c-0.603-1.705-1.953-3.056-3.658-3.658v-3.118c3.36 0.765 6.010 3.416 6.776 6.776zM16 18c-1.105 0-2-0.895-2-2s0.895-2 2-2c1.105 0 2 0.895 2 2s-0.895 2-2 2zM14 7.224v3.118c-1.705 0.603-3.056 1.953-3.658 3.658h-3.118c0.765-3.36 3.416-6.010 6.776-6.776zM7.224 18h3.118c0.603 1.705 1.953 3.056 3.658 3.658v3.118c-3.36-0.765-6.010-3.416-6.776-6.776zM18 24.776v-3.118c1.705-0.603 3.056-1.953 3.658-3.658h3.118c-0.765 3.36-3.416 6.010-6.776 6.776z" + }, "SWITCH.OFF": { "section": "Buttons", "title": "Off", diff --git a/ivette/src/frama-c/plugins/eva/Flamegraph.tsx b/ivette/src/frama-c/plugins/eva/Flamegraph.tsx new file mode 100644 index 00000000000..8e425f6a7c0 --- /dev/null +++ b/ivette/src/frama-c/plugins/eva/Flamegraph.tsx @@ -0,0 +1,172 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2024 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import React from 'react'; +import { IconButton } from 'dome/controls/buttons'; +import * as Ivette from 'ivette'; +import * as Ast from 'frama-c/kernel/api/ast'; +import * as States from 'frama-c/states'; +import * as Eva from 'frama-c/plugins/eva/api/values'; +import { FlameGraph } from 'react-flame-graph'; +import AutoSizer, { Size } from 'react-virtualized-auto-sizer'; +import { EvaReady, EvaStatus } from './components/AnalysisStatus'; +import { Inset } from 'dome/frame/toolbars'; +import { useFlipSettings } from 'dome'; + +// --- Flamegraph Table --- +interface Flamegraph { + kfKey?: string; + name: string; + value: number; + children?: Flamegraph[]; +} + +const addNodeToFlamegraph = ( + flamegraph: Flamegraph, + cs: string[], + row: Eva.evaFlamegraphData, +): void => { + // Accumulate times for all nodes crossed + flamegraph.value += row.time; + // updating last node + if(cs.length === 0) { + flamegraph.kfKey = row.kfkey; + return; + } + // Search/create next node + if (!flamegraph.children) flamegraph.children = []; + let nextNode = flamegraph.children.find((elt) => elt.name === cs[0]); + if (!nextNode) { + nextNode = { name: cs[0], value: 0 }; + flamegraph.children.unshift(nextNode); + } + cs.shift(); + // Treatment of the next node + addNodeToFlamegraph(nextNode, cs, row); +}; + +interface EvaFlamegraphProps { + useScope: boolean; + flameGraph: Flamegraph; + size: Size +} + +function EvaFlamegraph(props: EvaFlamegraphProps): JSX.Element { + const { useScope, flameGraph, size } = props; + const { width, height } = size; + const [ nodeInfos, setNodeInfos ] = React.useState(""); + + const changeScope = (f: Flamegraph): void => { + States.setCurrentScope(f.kfKey as Ast.decl); + }; + + return ( + <> + <FlameGraph + data={flameGraph} + height={height} + width={width} + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + onChange={(node:any) => { + if(useScope) changeScope(node.source); + }} + onMouseOver={(_e:Event, nodeInfos:Flamegraph) => { + const percentage = Math.round( + 10*(nodeInfos.value * 100)/flameGraph.value)/10; + const value = Math.round(nodeInfos.value*100)/100; + const infos = ( + nodeInfos.name+" : "+value+"s : "+percentage+"%" + ); + setNodeInfos(infos); + }} + onMouseOut={() => { setNodeInfos(""); }} + /> + { + nodeInfos && + <div className='flame-details'> + {nodeInfos} + </div> + } + </> + ); +} + +// --- Flamegraph Component --- +export function FlamegraphComponent(): JSX.Element { + const [useScope, flipUseScope] = + useFlipSettings("eva.flamegraph.scope", true); + const model = States.useSyncArrayData(Eva.evaFlamegraph); + + const flameGraph = React.useMemo<Flamegraph | null>(() => { + if(model.length === 0 ) return null; + const flame: Flamegraph = { + name: model[0].funlist.split(":")[0], + value: 0 + }; + model.forEach(row => { + const cs = row.funlist.split(":"); + cs.shift(); + addNodeToFlamegraph(flame, cs, row); + }); + return flame; + }, [model]); + + const isWaitingForData = !flameGraph || !flameGraph.children; + + return ( + <> + <Ivette.TitleBar > + <IconButton + icon="PIN" + kind={useScope ? "positive" : "default"} + onClick={flipUseScope} + title={useScope ? "Scope change enabled" : "Scope change disabled"} + /> + <Inset /> + <EvaStatus /> + </Ivette.TitleBar> + <EvaReady showChildrenForComputingStatus={!isWaitingForData} > + { + !isWaitingForData && + <AutoSizer key="flamegraph"> + {(size: Size) => ( + <EvaFlamegraph + useScope={useScope} + flameGraph={flameGraph} + size={size} + /> + )} + </AutoSizer> + } + </EvaReady> + </> + ); +} + +Ivette.registerComponent({ + id: 'fc.eva.flamegraph', + label: 'Eva Flamegraph', + title: 'Detailed flamegraph of the Eva analysis', + children: <FlamegraphComponent />, +}); + +// -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/plugins/eva/index.tsx b/ivette/src/frama-c/plugins/eva/index.tsx index d277a0fe8d7..18d19940d08 100644 --- a/ivette/src/frama-c/plugins/eva/index.tsx +++ b/ivette/src/frama-c/plugins/eva/index.tsx @@ -30,6 +30,7 @@ import './Summary'; import './Coverage'; import './DomainStates'; import './EvaSidebar'; +import './Flamegraph'; import './style.css'; // -------------------------------------------------------------------------- @@ -47,7 +48,8 @@ Ivette.registerView({ layout: { 'A': 'fc.eva.summary', 'B': 'fc.eva.coverage', - 'CD': 'fc.kernel.messages', + 'C': 'fc.kernel.messages', + 'D': 'fc.eva.flamegraph', }, }); diff --git a/ivette/src/frama-c/plugins/eva/style.css b/ivette/src/frama-c/plugins/eva/style.css index 41fb3879bf8..3b7861ae8ef 100644 --- a/ivette/src/frama-c/plugins/eva/style.css +++ b/ivette/src/frama-c/plugins/eva/style.css @@ -339,6 +339,17 @@ tr:first-of-type > .eva-table-callsite-box { .eva-status-icon.eva-computed { fill: var(--eva-alarms-true); } .eva-status-icon.eva-not_computed { fill: var(--eva-alarms-false); } +/* -------------------------------------------------------------------------- */ +/* --- Flamegraph --- */ +/* -------------------------------------------------------------------------- */ + +.flame-details { + padding: 3px 10px; + position: absolute; + bottom : 0; + background-color: var(--background-report); +} + /* -------------------------------------------------------------------------- */ /* --- Sidebar Eva --- */ /* -------------------------------------------------------------------------- */ diff --git a/ivette/src/frama-c/react-flame-graph.d.ts b/ivette/src/frama-c/react-flame-graph.d.ts new file mode 100644 index 00000000000..ff006266530 --- /dev/null +++ b/ivette/src/frama-c/react-flame-graph.d.ts @@ -0,0 +1,23 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2024 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +declare module 'react-flame-graph'; diff --git a/ivette/yarn.lock b/ivette/yarn.lock index 573ecf8a41a..2f199e30352 100644 --- a/ivette/yarn.lock +++ b/ivette/yarn.lock @@ -3223,6 +3223,11 @@ flatted@^3.1.0: resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz" integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== +flow-bin@^0.118.0: + version "0.118.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.118.0.tgz#fb706364a58c682d67a2ca7df39396467dc397d1" + integrity sha512-jlbUu0XkbpXeXhan5xyTqVK1jmEKNxE8hpzznI3TThHTr76GiFwK0iRzhDo4KNy+S9h/KxHaqVhTP86vA6wHCg== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz" @@ -4350,6 +4355,16 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + +memoize-one@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-3.1.1.tgz#ef609811e3bc28970eac2884eece64d167830d17" + integrity sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" @@ -4994,6 +5009,15 @@ react-fast-compare@^3.2.2: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== +react-flame-graph@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/react-flame-graph/-/react-flame-graph-1.4.0.tgz#52d118cc94348f630a812fc0ec530a5b73c30cdb" + integrity sha512-DaCK9ZX+xK0mNca72kUE5cu6T8hGe/KLsefQWf+eT9sVt+0WP1dVxZCGD8Svfn2KrZB9Mv011Intg/yG2YWSxA== + dependencies: + flow-bin "^0.118.0" + memoize-one "^3.1.1" + react-window "^1" + react-force-graph-2d@^1.25.4: version "1.25.4" resolved "https://registry.yarnpkg.com/react-force-graph-2d/-/react-force-graph-2d-1.25.4.tgz#91f9e8169d0eeb6a7e36c36dd99da5128702b776" @@ -5077,6 +5101,14 @@ react-virtualized@9.22.5: prop-types "^15.7.2" react-lifecycles-compat "^3.0.4" +react-window@^1: + version "1.8.10" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03" + integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@^18: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" -- GitLab