{"id":21814,"date":"2024-06-04T08:13:42","date_gmt":"2024-06-04T15:13:42","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/?p=21814"},"modified":"2024-06-04T08:14:57","modified_gmt":"2024-06-04T15:14:57","slug":"use-fluid-devtools-to-debug-your-web-app","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/use-fluid-devtools-to-debug-your-web-app\/","title":{"rendered":"Use Fluid DevTools to debug your web app"},"content":{"rendered":"<p><a href=\"https:\/\/fluidframework.com\/\">Fluid Framework<\/a> is a platform for building distributed, collaborative web apps that sync data in real time. Fluid saves developers from having to reinvent the wheel to solve the data sync problem, but developing and debugging Fluid apps can still be challenging, especially when dealing with complex data structures and many concurrent users.<\/p>\n<p>That&#8217;s why we created Fluid DevTools, a browser extension (available for <a href=\"https:\/\/aka.ms\/fluid\/devtool\/edge\">Edge<\/a> and <a href=\"https:\/\/aka.ms\/fluid\/devtool\/chrome\">Chrome<\/a>) that lets you inspect and manipulate Fluid containers and <a href=\"https:\/\/fluidframework.com\/docs\/data-structures\/overview\/\">DDSes (Distributed Data Structures)<\/a> within them when running your web app. Fluid DevTools give you a powerful tool to troubleshoot and test your Fluid app, as well as to learn more about how Fluid works under the hood. In this blog post, we will show how you can use Fluid DevTools to understand what Fluid is doing behind the scenes, and to troubleshoot a web app as you write it.<\/p>\n<p>Fluid DevTools is part of Fluid Framework 2, which is currently <a href=\"https:\/\/aka.ms\/fluid\/preview_blog\">in Preview<\/a> with general availability coming this summer. For questions, feedback and feature requests, please reach out to us on <a href=\"https:\/\/aka.ms\/fluid\/discuss\">GitHub<\/a>.<\/p>\n<h2>A quick overview of Fluid DevTools<\/h2>\n<p>Fluid DevTools can help you with the following tasks:<\/p>\n<ul>\n<li><strong>Shared data inspection &#8211;<\/strong> Inspect the contents of DDSes (such as <a href=\"https:\/\/fluidframework.com\/docs\/data-structures\/tree\/\">SharedTree<\/a>) in your Fluid containers.<\/li>\n<li><strong>Audience inspection &#8211;<\/strong> Look at the Fluid container\u2019s <a href=\"https:\/\/fluidframework.com\/docs\/build\/audience\/\">Audience<\/a>, the list of Fluid clients that are connected to the container.<\/li>\n<li><strong>Container state manipulation &#8211;<\/strong> Manually force a disconnection\/reconnection of the container, to see how your app code reacts to those scenarios.<\/li>\n<li><strong>Framework Telemetry<\/strong> &#8211; Look at telemetry generated by the framework for a deeper understanding of events.<\/li>\n<li><strong>Metrics visualization<\/strong> &#8211; View op-related metrics such as network latency, and time spent in outbound\/inbound processing.<\/li>\n<\/ul>\n<p>In the future you will also be able to manipulate the DDSes from within Fluid DevTools. Stay tuned for that!<\/p>\n<h2>Installing and setting up Fluid DevTools<\/h2>\n<p>The first thing to do is install the browser extension (for <a href=\"https:\/\/aka.ms\/fluid\/devtool\/edge\">Edge<\/a> or for <a href=\"https:\/\/aka.ms\/fluid\/devtool\/chrome\">Chrome<\/a>). You only need to do this once. Browsers will usually auto-update the installed extensions so you will always have the latest version.<\/p>\n<p>Then you need to configure your Fluid-based app to leverage Fluid DevTools. This will usually mean adding a few lines to your existing code:<\/p>\n<ul>\n<li>Call the createDevToolsLogger function to get a logger that DevTools can use to get Fluid telemetry from your app.\n<ul>\n<li>If you\u2019re already using a logger, you will wrap it in this step.<\/li>\n<\/ul>\n<\/li>\n<li>Once you have created or loaded the necessary Fluid containers for your app, call the initializeDevTools function passing the containers and the logger from the previous step.<\/li>\n<\/ul>\n<p>This will look something like this:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/alexvy86\/e1698e3ffabe281e64d849bcaf5571f3.js?file=sampleInitDevtools.ts\"><\/script><\/p>\n<p>Now start your app and navigate to it in the browser, open the browser\u2019s DevTools panel, and click on the icon for Fluid DevTools:<\/p>\n<p><img decoding=\"async\" width=\"872\" height=\"607\" class=\"wp-image-21816\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-2.png\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-2.png 872w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-2-300x209.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-2-768x535.png 768w\" sizes=\"(max-width: 872px) 100vw, 872px\" \/><\/p>\n<p>Optionally, you can opt in to share usage telemetry. It\u2019s completely anonymous and only tells us which parts of the Extension are the most useful. It will help us improve Fluid DevTools!<\/p>\n<p><img decoding=\"async\" width=\"825\" height=\"622\" class=\"wp-image-21817\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica.png\" alt=\"A screenshot of a computer Description automatically generated\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica.png 825w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-300x226.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-768x579.png 768w\" sizes=\"(max-width: 825px) 100vw, 825px\" \/><\/p>\n<h2>Using Fluid DevTools to troubleshoot a web app<\/h2>\n<p>Let\u2019s now see how we can use Fluid DevTools to investigate issues in a web app.<\/p>\n<p>The sample app I\u2019m using is a simplified version of <a href=\"https:\/\/aka.ms\/fluid\/devtools\/blog_sample_link\">this example in the Fluid Framework repository<\/a>. It displays a die and a button to roll it. It uses a simple SharedTree data structure with a \u2018value\u2019 property that stores the current value of the die. It looks like this:<\/p>\n<p><img decoding=\"async\" width=\"1944\" height=\"765\" class=\"wp-image-21818\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-1.png\" alt=\"A screenshot of a computer Description automatically generated\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-1.png 1944w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-1-300x118.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-1-1024x403.png 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-1-768x302.png 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-1-1536x604.png 1536w\" sizes=\"(max-width: 1944px) 100vw, 1944px\" \/><\/p>\n<p>But wait a second, the die is not showing correctly! It should look like the face of a six-sided die, with dots on it. Let\u2019s have a look at the data inside the Fluid container to see if we can find any clues. On the left menu, under \u201cContainers\u201d, click on the container you want to inspect. They are listed based on the unique identifiers you provide when initializing Fluid DevTools. In this case I used \u201cMy Container\u201d as identifier. After clicking the container, the \u201cData\u201d tab displays the Fluid data inside it:<\/p>\n<p><img decoding=\"async\" width=\"1648\" height=\"647\" class=\"wp-image-21819\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-5.png\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-5.png 1648w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-5-300x118.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-5-1024x402.png 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-5-768x302.png 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-5-1536x603.png 1536w\" sizes=\"(max-width: 1648px) 100vw, 1648px\" \/><\/p>\n<p>So far everything looks good but hovering over the information icon next to \u201cvalue\u201d (this is the field I defined in my SharedTree), I see that the data being stored in it is a string, when my app needs it to be a number for the rendering logic to work. And indeed, my SharedTree is currently set up to store a string.<\/p>\n<p><img decoding=\"async\" width=\"887\" height=\"189\" class=\"wp-image-21820\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-6.png\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-6.png 887w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-6-300x64.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-6-768x164.png 768w\" sizes=\"(max-width: 887px) 100vw, 887px\" \/><\/p>\n<p><script src=\"https:\/\/gist.github.com\/alexvy86\/e1698e3ffabe281e64d849bcaf5571f3.js?file=schema.ts\"><\/script><\/p>\n<p>Once I update my schema to store a number and fix some other places to account for that, the application renders correctly and I can confirm that the data stored in the Fluid container has the correct type.<\/p>\n<p><img decoding=\"async\" width=\"1650\" height=\"726\" class=\"wp-image-21822\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-8.png\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-8.png 1650w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-8-300x132.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-8-1024x451.png 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-8-768x338.png 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-8-1536x676.png 1536w\" sizes=\"(max-width: 1650px) 100vw, 1650px\" \/><\/p>\n<p>Now, I see a problem when a second client connects to the same Fluid container. When I roll the die in the first client, the change is reflected in the UI instantaneously in that client but takes some time to be reflected in the other one; and if the die is rolled in the second client, there\u2019s a delay before the UI is updated there, and as soon as it updates there it also updates in the first client at the same time.<\/p>\n<p style=\"text-align: center;\"><iframe src=\"\/\/www.youtube.com\/embed\/7gXR_YgJ30c\" width=\"560\" height=\"314\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>The video above showcases another feature of Fluid DevTools, the Op Latency chart (operations or \u201cops\u201d are the basic unit of change that Fluid operates on; for more details see <a href=\"https:\/\/fluidframework.com\/docs\/concepts\/architecture\">the documentation<\/a>). For each Op the current client submits to the service, the chart gets a new data point that tells you how much time it took for the op to be processed by the framework (both before being sent to the Fluid service and after it came back) and how much time it spent \u201cin network\u201d (i.e. being transmitted to\/from and processed by the service). This can help you identify if delays in your application are caused by network latency and\/or Fluid code.<\/p>\n<p>What we see in the Op Latency chart in the video suggests that all ops are taking pretty much the same amount of time (in the order of milliseconds) getting processed by Fluid (including processing and network time). The delay seems too big to be related to these times, so once again it looks like the issue might be in the application code.<\/p>\n<p>A bit of investigation reveals an interesting behavior: on a client that loads an existing container (in the video, the top client created the container, and the bottom client loaded it once it already existed) a lot of time is spent processing an event emitted by Fluid when the die value changes, delaying the UI update. That explains why the UI changes instantaneously on the top client when the die is rolled there but takes some time to change on the bottom client; and why when rolled on the bottom client, the UI takes some time to update there, but once it does the change is reflected on the top client immediately.<\/p>\n<p>For this demo, we deliberately introduced a delay (lines 12-16) to demonstrate this type of problem.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/alexvy86\/e1698e3ffabe281e64d849bcaf5571f3.js?file=index.tsx\"><\/script><\/p>\n<p>If you\u2019re interested in knowing more about what\u2019s happening behind the scenes in Fluid Framework, you can use the Telemetry -&gt; Events section to look at all the internal telemetry it generates. The following image shows it changing as I manually disconnected and reconnected the Container, also using Fluid DevTools (the Disconnect\/Reconnect button in the Container\u2019s details page).<\/p>\n<p><img decoding=\"async\" width=\"1430\" height=\"591\" class=\"wp-image-21824\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-3.png\" alt=\"A screenshot of a computer Description automatically generated\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-3.png 1430w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-3-300x124.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-3-1024x423.png 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/a-screenshot-of-a-computer-description-automatica-3-768x317.png 768w\" sizes=\"(max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p><img decoding=\"async\" width=\"937\" height=\"555\" class=\"wp-image-21825\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-11.png\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-11.png 937w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-11-300x178.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2024\/06\/word-image-21814-11-768x455.png 768w\" sizes=\"(max-width: 937px) 100vw, 937px\" \/><\/p>\n<p>The telemetry data displayed in DevTools can give you some insights into what\u2019s happening in the framework. If you are running into issues and need help, you can engage with us on <a href=\"https:\/\/aka.ms\/fluid\/discuss\">Github<\/a> and share some of this data. Fluid DevTools only displays the telemetry information within the extension and will only keep a limited number of events in memory. However, you can use the <a href=\"https:\/\/github.com\/microsoft\/FluidFramework\/tree\/main\/packages\/framework\/client-logger\/app-insights-logger\">@fluidframework\/app-insights-logger<\/a> package to create a logger that you can pass into to Fluid so that all generated telemetry gets forwarded to an <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/azure-monitor\/app\/app-insights-overview\">Azure Application Insights<\/a> instance you own. You can also use Application Insights as a persistent store for your telemetry and it allows for better querying and analysis of the events as necessary.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this blog post we described how to install the Fluid DevTools browser extension and how to configure your application to leverage it, and then showcased several of its features and how they can help you troubleshoot issues when developing a web application. We looked at the Fluid data visualizer, the Op Latency chart, the telemetry viewer (as well as how to send telemetry to Application Insights), and how you can manually disconnect\/reconnect a Fluid container.<\/p>\n<p>Visit the following resources to learn more:<\/p>\n<ul>\n<li><a href=\"https:\/\/aka.ms\/fluid\/start\" target=\"_blank\" rel=\"noopener\">Get Started<\/a>\u00a0with Fluid Framework 2.0<\/li>\n<li><a href=\"https:\/\/aka.ms\/fluid\/samples\" target=\"_blank\" rel=\"noopener\">Sample app code<\/a>\u00a0using SharedTree and SharePoint Embedded<\/li>\n<li>For questions or feedback,\u00a0<a href=\"https:\/\/aka.ms\/fluid\/discuss\" target=\"_blank\" rel=\"noopener\">reach out via Github<\/a><\/li>\n<li><a href=\"https:\/\/aka.ms\/fluid\/connect\" target=\"_blank\" rel=\"noopener\">Connect directly with the Fluid team<\/a>, we would love to hear what you are building!<\/li>\n<li>Follow\u00a0<a href=\"http:\/\/twitter.com\/fluidframework\" target=\"_blank\" rel=\"noopener\">@FluidFramework on X (Twitter)<\/a>\u00a0to stay updated<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>A guide to using the Fluid DevTools browser extension to inspect and manipulate Fluid data structures and containers.<\/p>\n","protected":false},"author":161486,"featured_media":21843,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[6],"tags":[333,118,302],"class_list":["post-21814","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-fluid-framework","tag-fluid-devtools","tag-fluid-framework","tag-sharedtree"],"acf":[],"blog_post_summary":"<p>A guide to using the Fluid DevTools browser extension to inspect and manipulate Fluid data structures and containers.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/21814","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/users\/161486"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/comments?post=21814"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/21814\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media\/21843"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media?parent=21814"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/categories?post=21814"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/tags?post=21814"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}