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