diff --git a/.tool-versions b/.tool-versions
new file mode 100644
index 00000000..7411ef96
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1 @@
+nodejs 16.19.1
diff --git a/docs/graph-tooltip-details.mdx b/docs/graph-tooltip-details.mdx
index 66e6ae2a..a7326856 100644
--- a/docs/graph-tooltip-details.mdx
+++ b/docs/graph-tooltip-details.mdx
@@ -30,11 +30,11 @@ The name of the file that the function is in. If the file is not in the project,
## Address
-The address of the function in memory. This is a pointer to the function in memory. The addresses are displayed in hexadecimal and are prefixed with `0x`.
+The address of the function in memory. This is a pointer to the function in memory. The addresses are displayed in hexadecimal and are prefixed with `0x`.
## Binary
-The name of the binary that the function is in. Using the [Binary-based Color Stack](./flamegraph-binary-based-colour-stack.mdx) feature, you can identify the most expensive binaries in the rendered flamegraph.
+The name of the binary that the function is in. Using the [Binary-based Color Stack](./icicle-graph-binary-based-colour-stack.mdx) feature, you can identify the most expensive binaries in the rendered flamegraph.
## Build ID
diff --git a/docs/icicle-graph-anatomy.mdx b/docs/icicle-graph-anatomy.mdx
new file mode 100644
index 00000000..a7a79aaf
--- /dev/null
+++ b/docs/icicle-graph-anatomy.mdx
@@ -0,0 +1,413 @@
+# Anatomy of icicle and flame graphs
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import Animation from '@site/src/components/Animation';
+
+In the simplest terms, an icicle graph or a flame graph shows a program's call stack. But what does that mean?
+
+## Anatomy of a single profile
+
+### Structure
+
+The graphs represent the call stack data as a series of horizontally-aligned rectangles.
+Each rectangle corresponds to a function, and its width represents the time spent in that function,
+including the resource (time, memory, etc.) used in its child functions.
+
+The rectangles are stacked on top of one another, with the parent function at the bottom and the child functions above it.
+The hierarchy and the relationship between different functions can be easily understood by following the vertical alignment of the rectangles.
+
+:::info
+
+The critical aspects of icicle and flame graphs are:
+- The width of each rectangle represents the relative resource usage for a function, including the time spent in its child functions.
+- The height of the rectangles does NOT represent anything. The vertical alignment of the rectangles is used to describe the call stack hierarchy.
+
+:::
+
+### Colors
+
+In both icicle and flame graphs, colors are used to visually differentiate functions and make the graphs more readable.
+The colors usually do not carry any specific meaning related to performance data.
+Instead, they are chosen to make distinguishing between different functions and their relationships within the call stack hierarchy easier.
+
+Typically, colors are assigned randomly or based on a hashing algorithm applied to the function names.
+This ensures that adjacent rectangles representing different functions have contrasting colors,
+making it easier to identify and trace individual functions within the graph.
+
+However, some tools may allow you to customize the color scheme or use colors to represent additional information,
+such as memory usage or specific categories of functions (e.g., I/O-bound, CPU-bound, etc.).
+
+In such cases, the color assignments and their meanings depend on the specific tool or configuration used to generate the icicle or flame graph.
+Always refer to the documentation or legend accompanying the graph to understand the color representation if it carries any specific meaning in your particular case.
+
+For example, in parca.dev we use the following color palette for icicle graphs:
+
+
+
+
+
+
+
+:::info
+Conventionally, the top-to-bottom order of the call stacks looks like icicles. And the color palette is chosen from rather cold colors.
+
+On the other hand, the bottom-to-top order of the call stacks looks like flames. And the color palette is chosen to represent a flame.
+:::
+
+:::caution
+The colors usually do not carry any specific meaning related to performance data.
+
+If the rectangle are red in color, it does NOT mean that the function is taking relatively more resources.
+:::
+
+---
+
+Easy, right? Let's see how we can visualize a single call stack collected from a program.
+
+### Walk-through
+
+Let us see how we can visualize a single call stack collected from a program.
+
+The following program calls a function `a` which calls a function `b` which calls a function `c` and then loops forever.
+
+```go
+package main
+
+func c() {
+ for {}
+}
+
+func b() {
+ c()
+}
+
+func a() {
+ b()
+}
+
+func main() {
+ a()
+}
+```
+
+---
+
+:::caution
+
+The colors usually do not carry any specific meaning related to performance data.
+
+:::
+
+When we profile the above program for let say `50ms`, we get the following profile represented as flame or icicle graph.
+
+
+
+
+The key aspect of icicle graphs is that they are drawn from **the top to the bottom**.
+
+
+
+
+
+
+The key aspect of flame graphs is that they are drawn from **the bottom to the top**.
+
+
+
+
+
+
+Even though, in this example, the majority of time is spent in the function `c`, all the rectangles have the same width.
+
+:::info This is because the width of the rectangle for a function represents the time spent in that function and its child functions.
+:::
+
+---
+
+Let's add more calls to `c` and see how the graph changes.
+
+```diff
+package main
+
+func c() {
+- for {}
++ for i := 0; i < 1_000_000; i++ {}
+}
+
+func b() {
+ c()
++ c()
++ c()
+}
+
+func a() {
+ b()
+}
+
+func main() {
+ a()
+}
+```
+
+
+
+
+The key aspect of icicle graphs is that they are drawn from **the top to the bottom**.
+
+
+
+
+
+
+The key aspect of flame graphs is that they are drawn from **the bottom to the top**.
+
+
+
+
+
+
+It doesn't seem like something has changed, right? Why is that?
+This is called `merging`. Even though, it takes more time to execute the function calls in total, relative to the other functions, the width of the rectangle for `c` remains the same.
+It doesn't matter how many times we call the same function at the same level.
+We will discuss this in detail later.
+
+---
+
+Let's go back to the original program and add a call to a new function `d` and `e` in the function `b`.
+
+```diff
+
+```diff
+package main
+
+func c() {
+ for i := 0; i < 1_000_000; i++ {}
+}
+
++func d() {
++ for i := 0; i < 1_000_000; i++ {}
++}
++
++func e() {
++ for i := 0; i < 1_000_000; i++ {}
++}
+
+
+func b() {
+ c()
++ d()
++ e()
+}
+
+func a() {
+ b()
+}
+
+func main() {
+ a()
+}
+```
+
+You can see that the rectangle for the function `b` is now have three rectangles as its children.
+And the rectangle for the function `b` covers them call.
+
+:::info This is because the width of the rectangle for the function `b` represents the time spent in the function `b` and its child functions.
+:::
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+In a similar manner, let's re-arrange the code a bit. We will move the call to `c` from the function `b` to the function `a`.
+And we will add a call to the function `f` in the function `b`. And have a `c` exactly the same as `b`.
+The code will look like this:
+
+```diff
+package main
+
+func c() {
+- for i := 0; i < 1_000_000; i++ {}
++ d()
++ e()
++ f()
+}
+
+func d() {
+ for i := 0; i < 1_000_000; i++ {}
+}
+
+func e() {
+ for i := 0; i < 1_000_000; i++ {}
+}
++
++func f() {
++ for i := 0; i < 1_000_000; i++ {}
++}
++
+func b() {
+- c()
+ d()
+ e()
++ f()
+}
+
+func a() {
+ b()
++ c()
+}
+
+func main() {
+ a()
+}
+```
+
+You can see that the rectangle for the function `a` is now have two rectangles as its children.
+And the `b` and `c` rectangles are now children of the `a` rectangle. And the `d`, `e` and `f` rectangles are now children of the `b` and `c` rectangles.
+But the leaf rectangles are not merged.
+
+Hmm, why is that?
+
+> It's because even though the functions `d`, `e` and `f` are same and are in the same level, they are not called from the same function.
+
+They have different parents/ancestry. So, they are not merged.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+This is how we visualize a single call stack collected from a program.
+But happens when we collect multiple call stacks over time?
+
+Let's see.
+
+## Anatomy of Continuous profiling
+
+In the life-cycle of a program, there are many call stacks.
+Even in a second, a program can have thousands of calls and in a profile that we collected from a program for a minute,
+there can be millions of call stacks. And when you profile a host with multiple programs running, there can be billions of call stacks.
+
+We need to reduce collected data to feasibly manage it. For that we take samples of call stacks for a given interval and then aggregate them.
+
+There are three major steps in the process of aggregating call stacks and building a profile.
+Let's see them one by one. And visualize the process with animations using the graphs.
+
+### Sampling
+
+Sampling is the process of taking a sample of call stacks for a given interval.
+
+You can see below that we take samples for a given interval (e.g. 50ms), over a period of time (e.g. 1 minute).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Sorting
+
+:::info Stack traces in a graph are sorted alphabetically.
+:::
+
+We sort the samples alphabetically, because even the samples are taken for a given interval are aggregated and the order of the samples is not guaranteed.
+And our goal is to determine the bottlenecks of the programs. Thus we do not need to keep the order of the samples.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Merging (Grouping)
+
+Lastly, we merge the samples that have the same call stack.
+
+:::caution We only merge the samples that have the same call stack.
+:::
+
+We do not merge the samples that have the same function name, even if they are in the level.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+----
+
+Let's see everything together.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+This is how a profile collected and represented in icicle graphs or flame graphs can provide valuable insights into the performance of your software,
+enabling you to identify bottlenecks and optimize code effectively.
+
+By visualizing the call stack data clearly and intuitively,
+these graphs make it easier for developers to understand the complex relationships between functions and their resource consumption.
+
+Whether you choose to work with icicle graphs, which emphasize cumulative time spent in functions and their children, or flame graphs,
+which focuses on the self-time of individual functions,
+these powerful visualization tools can significantly enhance your ability to analyze performance data,
+and improve your software's performance and user experience.
+
+We hope you enjoyed this article and learned something new :)
+
+##### Sources
+
+- https://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html
+- https://queue.acm.org/detail.cfm?id=2927301
+- https://youtu.be/6uKZXIwd6M0
+- https://youtu.be/6uKZXIwd6M0
+- https://www.webperf.tips/tip/understanding-flamegraphs/
diff --git a/docs/flamegraph-binary-based-colour-stack.mdx b/docs/icicle-graph-binary-based-colour-stack.mdx
similarity index 100%
rename from docs/flamegraph-binary-based-colour-stack.mdx
rename to docs/icicle-graph-binary-based-colour-stack.mdx
diff --git a/docs/icicle-graph-interpretation.mdx b/docs/icicle-graph-interpretation.mdx
new file mode 100644
index 00000000..5e73d6c0
--- /dev/null
+++ b/docs/icicle-graph-interpretation.mdx
@@ -0,0 +1,103 @@
+# How to interpret icicle and flame graphs?
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import BrowserWindow from '@site/src/components/BrowserWindow';
+
+Have you ever faced performance issues while developing or maintaining a software application?
+Visualizing and analyzing performance data can be quite a challenge, especially when dealing with complex systems.
+
+Enter icicle and flame graphs - two powerful visualization tools that can help you identify bottlenecks and optimize your code.
+
+In this document, we'll delve into their importance, how they work, and how you can use them to improve your software's performance.
+
+### Why?
+
+Performance optimization is crucial for any software application, as it can greatly impact the user experience and system resources.
+However, identifying the causes of performance issues can be a daunting task, especially when dealing with large code-bases and complex call stacks.
+This is where icicle and flame graphs come in handy.
+
+1. **Visualizing complex data:** Icicle and flame graphs provide a graphical representation of the call stack data, making it easier to visualize and understand the relationships between different functions and their resource consumption.
+
+2. **Identifying bottlenecks:** By visualizing the call stack data, icicle and flame graphs enable developers to identify performance bottlenecks, such as functions consuming a significant amount of CPU time or memory.
+
+3. **Comparing different profiles:** Icicle and flame graphs can be used to compare different profiles, helping developers understand the impact of changes in the code-base and make informed decisions about potential optimizations.
+
+4. **Platform-agnostic:** Both icicle and flame graphs can be generated from various programming languages and platforms, making them versatile tools for developers working on diverse projects.
+
+### How?
+
+#### Finding Performance Issues
+
+1. **Look for wide blocks:**
+Start by analyzing the icicle or flame graph to identify functions that consume a significant amount of resources, such as CPU time or memory.
+These functions are usually represented by wider rectangles in the graph, indicating that they are taking up a large portion of the total execution time.
+Make a list of these functions as potential targets for optimization.
+
+:::tip Wider blocks indicate a higher resource usage or longer duration. Focus on these blocks to identify potential bottlenecks.
+:::
+
+2. **Examine the call stack:**
+Examine the call stack hierarchy to understand the relationship between different functions.
+This will help you identify if the performance issue is caused by a single function or a combination of functions.
+Understanding the call stack can also reveal opportunities for optimization, such as refactoring or eliminating redundant calls.
+
+3. **Look for tall stacks:**
+In flame graphs, tall stacks indicate deep call hierarchies, which may signify complex and inefficient code.
+
+2. **Examine recurring patterns:**
+Repeated patterns in the graph may indicate redundant or repetitive code that could be optimized.
+
+##### Comparing Versions
+
+1. **Overlay graphs:**
+Overlay two graphs to compare different versions of the same system, making it easy to identify improvements or regressions.
+
+2. **Analyze differences:**
+Look for changes in block width or stack height to determine if specific functions have become more or less efficient.
+
+:::tip
+You're lucky! We got you covered. You can easily compare graphs using Parca.
+Go and check it out! https://demo.parca.dev
+:::
+
+
+
+
+
+
+
+Icicle and flame graphs are powerful tools for visualizing and analyzing system performance.
+By understanding how to interpret these graphs, you can identify bottlenecks and optimize resource usage within your system.
+
+#### Optimize the code
+
+Once you have identified the problematic functions and analyzed the call stack, start optimizing the code. This may involve:
+
+a. **Optimizing algorithms:**
+Replace inefficient algorithms with more efficient ones or use data structures that provide better performance for the specific use case.
+
+b. **Reducing function call overhead:** Minimize the number of function calls, especially in performance-critical code paths.
+
+c. **Parallelizing code:**
+If the performance bottleneck is caused by CPU-bound operations, consider parallelizing the code to take advantage of multiple processor cores.
+
+d. **Reducing memory usage:**
+Optimize memory allocation and de-allocation, and minimize memory fragmentation.
+
+#### Measure the impact
+
+After making optimizations, collect new profiling data and generate updated icicle or flame graphs.
+Compare the new graphs with the previous ones to measure the impact of your changes.
+If the optimizations have been successful, you should see a reduction in the width of the problematic functions and an overall improvement in the application's performance.
+
+#### Iterate and refine
+
+Performance optimization is an iterative process.
+Continue to analyze the updated graphs, identify new bottlenecks, and optimize your code accordingly.
+Keep refining your optimizations until you achieve the desired performance level.
+
+You can leverage icicle and flame graphs to optimize your software's performance effectively.
+
+By identifying bottlenecks, understanding the call stack hierarchy, optimizing code, measuring the impact, and iterating on the process,
+you can significantly enhance the performance and user experience of your software application.
diff --git a/docs/icicle-graph-understanding.mdx b/docs/icicle-graph-understanding.mdx
new file mode 100644
index 00000000..cc444750
--- /dev/null
+++ b/docs/icicle-graph-understanding.mdx
@@ -0,0 +1,174 @@
+# Understanding icicle and flame graphs
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import BrowserWindow from '@site/src/components/BrowserWindow';
+
+
+Icicle and flame graphs are essential visualization tools for analyzing performance data in various systems, like computer software, database systems, and web services.
+These graphs visually represent a system's performance, making it easier to identify bottlenecks and optimize resource usage.
+
+This document will guide you through understanding and interpreting icicle and flame graphs to improve your system's performance.
+
+:::info To learn more about mechanics of icicle and flame graphs, check out the [Anatomy of icicle and flame graphs](/docs/icicle-graph-anatomy) page.
+:::
+
+
+Let's take a look at how to interpret icicle and flame graphs.
+
+
+
+
+Here's a quick summary of how to interpret icicle graphs:
+
+1. Each rectangle represents a function call in the stack.
+2. The y-axis shows stack depth (number of frames on the stack). The bottom rectangle shows the function that was consuming cumulatively the most resource. Everything above represents ancestry. The function above a function is its parent.
+3. The x-axis spans the samples. It does not show the passing of time, as most of the time-series graphs do. It's sorted alphabetically to maximize merging.
+4. The width of the rectangle shows the total resource usage. Functions with wide rectangles may consume more resource per execution than narrow rectangles, or, they may simply be called more often.
+
+:::caution The colors do not represent anything significant.
+
+This visualization was called an "icicle graph", it looks like icicles.
+They are usually picked at random to be cold colors (other meaningful palettes are supported).
+:::
+
+
+
+
+
+
+Here's a quick summary of how to interpret flame graphs:
+
+1. Each rectangle represents a function call in the stack. The width of each rectangle represents the relative resource usage for a function, including the time spent in its child functions.
+2. The y-axis shows stack depth (number of frames on the stack). The top rectangle shows the function that consuming cumulatively the most resource. Everything beneath represents ancestry. The function beneath a function is its parent.
+3. The x-axis spans the samples. It does not show the passing of time, as most of the time-series graphs do. It's sorted alphabetically to maximize merging.
+4. The width of the rectangle shows the total resource usage. Functions with wide rectangles may consume more resource per execution than narrow rectangles, or, they may simply be called more often.
+
+:::caution The colors do not represent anything significant.
+
+This visualization was called an "flame graph", it looks like flames. So the colors are chosen to be warm colors.
+They are usually picked at random to be warm colors (other meaningful palettes are supported).
+:::
+
+
+
+
+
+
+
+
+
+
+Let's take a look at an example profile.
+
+
+
+**The graph is a tree.** The root of the tree is the function that was consuming the most resource.
+But it's cumulative. It's the sum of all the resources consumed by the function and its children.
+
+
+
+**The bottom edge of the graph shows the function that was solely consuming the mostly resource.**
+
+In this case, they are `d`, `f`, and `c`.
+
+
+
+**The vertical axis shows the stack depth.**
+
+For example, one of the leaf is `f` function.
+The function above it is `f`'s parent, which is `f`'s caller. In this case, it's `e`. The function above `e` is `b`, and so on.
+
+
+
+**We can visually compare the length of the rectangles to see which function is consuming more resources.**
+
+For example, in this case, `b` is consuming more resources than `c`.
+
+
+
+**The horizontal axis shows the samples.**
+
+:::caution It does not show the passing of time, as most of the time-series graphs do.
+:::
+
+
+
+
+
+
+Let's take a look at an example profile.
+
+
+
+**The graph is a tree.** The root of the tree is the function that was consuming the most resource.
+But it's cumulative. It's the sum of all the resources consumed by the function and its children.
+
+
+
+**The top edge of the graph shows the function that was solely consuming the mostly resource.**
+
+In this case, they are `d`, `f`, and `c`.
+
+
+
+**The vertical axis shows the stack depth.**
+
+For example, one of the leaf is `f` function.
+The function above it is `f`'s parent, which is `f`'s caller. In this case, it's `e`. The function above `e` is `b`, and so on.
+
+
+
+**We can visually compare the length of the rectangles to see which function is consuming more resources.**
+
+For example, in this case, `b` is consuming more resources than `c`.
+
+
+
+**The horizontal axis shows the samples.**
+
+:::caution It does not show the passing of time, as most of the time-series graphs do.
+:::
+
+
+
+
+
+
+### Real-life Examples
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+##### Sources
+
+- https://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html
+- https://queue.acm.org/detail.cfm?id=2927301
+- https://youtu.be/6uKZXIwd6M0
+- https://youtu.be/6uKZXIwd6M0
diff --git a/docusaurus.config.js b/docusaurus.config.js
index be507fbc..92f6f211 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -21,7 +21,17 @@ module.exports = {
],
plugins: [
require.resolve("./docusaurus-github-releases-plugin/src/index.js"),
+ () => ({
+ configureWebpack() {
+ return {
+ module: {
+ rules: [{ test: /\.riv$/, use: "file-loader" }],
+ },
+ };
+ },
+ }),
],
+ staticDirectories: ["public", "static"],
themeConfig: {
announcementBar: {
id: "github_star",
@@ -51,9 +61,9 @@ module.exports = {
},
{
type: "doc",
- docId: "overview",
+ docId: "demo",
position: "left",
- label: "Documentation",
+ label: "Demo",
},
{
type: "doc",
diff --git a/package.json b/package.json
index 42cdd900..7d7a7df6 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,9 @@
"name": "parca-dev",
"version": "0.0.0",
"private": true,
- "engines": { "node": ">=16.0.0" },
+ "engines": {
+ "node": ">=16.0.0"
+ },
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
@@ -19,6 +21,7 @@
"@docusaurus/preset-classic": "^2.2.0",
"@docusaurus/theme-search-algolia": "^2.2.0",
"@mdx-js/react": "^1.6.21",
+ "@rive-app/react-canvas": "^3.0.38",
"@svgr/webpack": "^6.5.1",
"clsx": "^1.2.1",
"file-loader": "^6.2.0",
diff --git a/sidebars.js b/sidebars.js
index 7bbdf14d..47105f77 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -27,12 +27,17 @@ module.exports = {
},
{
type: "doc",
- label: "Demo",
- id: "demo",
+ label: "Concepts",
+ id: "concepts",
+ },
+ {
+ type: "doc",
+ label: "FAQ",
+ id: "faq",
},
{
type: "category",
- label: "Parca",
+ label: "Server",
items: [
"parca",
"ingestion",
@@ -56,23 +61,20 @@ module.exports = {
},
{
type: "category",
- label: "Profile Visualization",
+ label: "Visualization",
items: [
- {
- type: "category",
- label: "Flamegraph",
- items: [
- "flamegraph-binary-based-colour-stack",
- "graph-tooltip-details",
- ],
- },
+ "icicle-graph-anatomy",
+ "icicle-graph-understanding",
+ "icicle-graph-interpretation",
+ "icicle-graph-binary-based-colour-stack",
+ "graph-tooltip-details",
],
},
],
},
{
type: "category",
- label: "Parca Agent",
+ label: "Agent",
items: [
"parca-agent",
"parca-agent-design",
@@ -90,21 +92,11 @@ module.exports = {
label: "Debuginfo CLI",
id: "debuginfo-cli",
},
- {
- type: "doc",
- label: "Concepts",
- id: "concepts",
- },
{
type: "category",
label: "Grafana Plugin",
items: ["grafana-flamegraph-plugin", "grafana-datasource-plugin"],
},
- {
- type: "doc",
- label: "FAQ",
- id: "faq",
- },
{
type: "doc",
label: "Governance",
@@ -115,7 +107,7 @@ module.exports = {
label: "Community",
id: "community",
},
- {
+ {
type: "doc",
label: "Resources",
id: "talks",
@@ -144,5 +136,10 @@ module.exports = {
},
],
},
+ {
+ type: "doc",
+ label: "Demo",
+ id: "demo",
+ },
],
};
diff --git a/src/components/Animation/index.tsx b/src/components/Animation/index.tsx
new file mode 100644
index 00000000..260c498f
--- /dev/null
+++ b/src/components/Animation/index.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+import {useRive, Layout, Fit, Alignment} from '@rive-app/react-canvas';
+
+import styles from "./styles.module.css";
+
+interface Props {
+ src: string;
+}
+
+function Animation({ src, ...props }: Props) {
+ const {rive, RiveComponent} = useRive({
+ src: src,
+ autoplay: false,
+ // https://help.rive.app/runtimes/layout
+ layout: new Layout({fit: Fit.Fill, alignment: Alignment.Center}),
+ });
+
+
+ return (
+