{"id":2792,"date":"2017-08-01T13:47:17","date_gmt":"2017-08-01T13:47:17","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=2792"},"modified":"2024-12-19T09:38:39","modified_gmt":"2024-12-19T09:38:39","slug":"profiling-web-applications-golang","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/profiling-web-applications-golang\/","title":{"rendered":"Profiling web applications in Golang"},"content":{"rendered":"<p>I&#8217;ve been watching the 2017 Gophercon videos from\u00a0<a href=\"http:\/\/tqdev.com\/2017-gophercon-2017-videos-online\">here<\/a>. There are many good talks that I would recommend watching from that list.<\/p>\n<p>One that I really wanted to try out on my own was <a href=\"https:\/\/www.youtube.com\/watch?v=ha8gdZ27wMo\">the profiling presentation that Peter Bourgon did<\/a>. I assume for the sake of simplicity, he left some details out. I&#8217;ve been trying to figure them out on my own.<\/p>\n<p>I was inspired to try to profile my own Golang web apps using the method he presented in his talk. So in this post I&#8217;ll show you a simplified example of how to profile your own web applications in more detail.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_68_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title \" >Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/intelligentbee.com\/blog\/profiling-web-applications-golang\/#The_web_app\" title=\"The web app\">The web app<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/intelligentbee.com\/blog\/profiling-web-applications-golang\/#The_CPU_profile\" title=\"The CPU profile\">The CPU profile<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/intelligentbee.com\/blog\/profiling-web-applications-golang\/#Optimizations\" title=\"Optimizations\">Optimizations<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/intelligentbee.com\/blog\/profiling-web-applications-golang\/#A_better_comparison\" title=\"A better comparison\">A better comparison<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/intelligentbee.com\/blog\/profiling-web-applications-golang\/#Profiling_memory\" title=\"Profiling memory\">Profiling memory<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/intelligentbee.com\/blog\/profiling-web-applications-golang\/#Conclusion\" title=\"Conclusion\">Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"The_web_app\"><\/span>The web app<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>To see a practical example of profiling and to keep it simple, we need to create a web app that when called on a particular route, performs some sort of calculation and returns a result in it&#8217;s payload.<\/p>\n<p>If we were to mimic real life scenarios, we would be here all day. So just to keep it simple, we will create a single route that calculates the 25th Fibonacci number when it&#8217;s called and returns it in the response body.<\/p>\n<p>I&#8217;ve written two versions of the Fibonacci function before hand, one that performs recursion (<em>exponential time<\/em> &#8211; very CPU intensive) and one that calculates the number using a vector (<em>linear time<\/em> &#8211; not very CPU intensive).<\/p>\n<p>In order to use the profiling tool, you need to import the <span class=\"lang:default decode:true crayon-inline \">net\/http\/pprof<\/span>\u00a0 package and register some routes. In the presentation I mentioned earlier, the speaker mentioned that<em> we could leave this imported even in production environments since it does not affect performance<\/em>.<\/p>\n<pre class=\"lang:go decode:true\" title=\"Fibonacci web server\">package main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"log\"\r\n\t\"net\/http\"\r\n\t\"net\/http\/pprof\"\r\n)\r\n\r\n\/\/ O(n) Fibonacci\r\nfunc linearFibonacci(n int) int {\r\n\t\/\/ Create an int array of size n + 1\r\n\tv := make([]int, n+1)\r\n\r\n\t\/\/ F(0) = 0\r\n\tv[0] = 0\r\n\t\/\/ F(1) = 1\r\n\tv[1] = 1\r\n\r\n\t\/\/ F(i) = F(i-1) + F(i-2)\r\n\tfor i := 2; i &lt;= n; i++ {\r\n\t\tv[i] = v[i-1] + v[i-2]\r\n\t}\r\n\r\n\t\/\/ F(n) - return the n-th Fibonacci number\r\n\treturn v[n]\r\n}\r\n\r\n\/\/ O(2^n) Fibonacci\r\nfunc exponentialFibonacci(n int) int {\r\n\t\/\/ F(0) = 0\r\n\tif n == 0 {\r\n\t\treturn 0\r\n\t}\r\n\r\n\t\/\/ F(1) = 1\r\n\tif n == 1 {\r\n\t\treturn 1\r\n\t}\r\n\r\n\t\/\/ F(n) = F(n-1) + F(n-2) - return the n-th Fibonacci number\r\n\treturn exponentialFibonacci(n-1) + exponentialFibonacci(n-2)\r\n}\r\n\r\n\/\/ HTTP request handler\r\nfunc handler(w http.ResponseWriter, r *http.Request) {\r\n\t\/\/ return the 25th Fibonacci number in the response payload\r\n\tfmt.Fprintf(w, \"%d\", exponentialFibonacci(25))\r\n}\r\n\r\nfunc main() {\r\n\t\/\/ Create a new HTTP multiplexer\r\n\tmux := http.NewServeMux()\r\n\r\n\t\/\/ Register our handler for the \/ route\r\n\tmux.HandleFunc(\"\/\", handler)\r\n\r\n\t\/\/ Add the pprof routes\r\n\tmux.HandleFunc(\"\/debug\/pprof\/\", pprof.Index)\r\n\tmux.HandleFunc(\"\/debug\/pprof\/cmdline\", pprof.Cmdline)\r\n\tmux.HandleFunc(\"\/debug\/pprof\/profile\", pprof.Profile)\r\n\tmux.HandleFunc(\"\/debug\/pprof\/symbol\", pprof.Symbol)\r\n\tmux.HandleFunc(\"\/debug\/pprof\/trace\", pprof.Trace)\r\n\r\n\tmux.Handle(\"\/debug\/pprof\/block\", pprof.Handler(\"block\"))\r\n\tmux.Handle(\"\/debug\/pprof\/goroutine\", pprof.Handler(\"goroutine\"))\r\n\tmux.Handle(\"\/debug\/pprof\/heap\", pprof.Handler(\"heap\"))\r\n\tmux.Handle(\"\/debug\/pprof\/threadcreate\", pprof.Handler(\"threadcreate\"))\r\n\r\n\t\/\/ Start listening on port 8080\r\n\tif err := http.ListenAndServe(\":8080\", mux); err != nil {\r\n\t\tlog.Fatal(fmt.Sprintf(\"Error when starting or running http server: %v\", err))\r\n\t}\r\n}<\/pre>\n<p>As you can see, this is a really simple application, save it to a file called\u00a0<span class=\"lang:default decode:true crayon-inline \">main.go<\/span>\u00a0and build it like this:\u00a0<span class=\"lang:default decode:true crayon-inline \">go build -o myserver main.go<\/span>\u00a0.<\/p>\n<p>Now you can run your binary:\u00a0<span class=\"lang:default decode:true crayon-inline \">.\/myserver<\/span>\u00a0and to check if it&#8217;s working we&#8217;ll send a request to it:<\/p>\n<pre class=\"lang:default decode:true\">$ curl http:\/\/localhost:8080\r\n75025<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"The_CPU_profile\"><\/span>The CPU profile<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now, while the server is running, you will need to run two commands in parallel. You first need to start the profiling tool which will record data for 30 seconds after it is run AND as it is running, run the <a href=\"https:\/\/httpd.apache.org\/docs\/2.4\/programs\/ab.html\">Apache Benchmark tool<\/a> to send a few requests it&#8217;s way.<\/p>\n<p>So you will need to run the profiling tool like this:<\/p>\n<p><span class=\"wrap:true lang:default decode:true crayon-inline\">go tool pprof -seconds 30 myserver http:\/\/localhost:8080\/debug\/pprof\/profile<\/span><\/p>\n<p>While that&#8217;s running, run the benchmark:<\/p>\n<p><span class=\"lang:default decode:true crayon-inline \">ab -k -c 8 -n 100000 &#8220;http:\/\/127.0.0.1:8080\/&#8221;<\/span><\/p>\n<p>This will create a total of 100,000 keep-alive requests to your server, using 8 cores. To better understand what this benchmark command does, it is explained in the link provided.<\/p>\n<p>After 30 seconds, the profiling tool will show a prompt for commands that will look something like this:<\/p>\n<pre class=\"lang:default decode:true \">$ go tool pprof -seconds 30 myserver http:\/\/localhost:8080\/debug\/pprof\/profile\r\nFetching profile from http:\/\/localhost:8080\/debug\/pprof\/profile?seconds=30\r\nPlease wait... (30s)\r\nSaved profile in \/Users\/username\/pprof\/pprof.myserver.localhost:8080.samples.cpu.013.pb.gz\r\nEntering interactive mode (type \"help\" for commands)\r\n(pprof)<\/pre>\n<p>Here you can run commands to show you how much of CPU time each function took and other useful information. For example, if I run <span class=\"lang:default decode:true crayon-inline \">top5<\/span>\u00a0 it will list the top 5 functions that are hogging the CPU:<\/p>\n<pre class=\"lang:default decode:true\">(pprof) top5\r\n84.57s of 85.18s total (99.28%)\r\nDropped 87 nodes (cum &lt;= 0.43s)\r\nShowing top 5 nodes out of 30 (cum &gt;= 1.09s)\r\n      flat  flat%   sum%        cum   cum%\r\n    51.93s 60.97% 60.97%     51.93s 60.97%  main.exponentialFibonacci\r\n    20.57s 24.15% 85.11%     20.59s 24.17%  fmt.(*pp).doPrintf\r\n    12.06s 14.16% 99.27%     12.06s 14.16%  syscall.Syscall\r\n     0.01s 0.012% 99.28%     11.22s 13.17%  net.(*netFD).Write\r\n         0     0% 99.28%      1.09s  1.28%  bufio.(*Reader).ReadLine<\/pre>\n<p>As you can see the <span class=\"lang:default decode:true crayon-inline \">exponentialFibonacci<\/span>\u00a0function really hogs the CPU.<\/p>\n<p><strong>Please note:<\/strong>\u00a0These values might differ on your machine. For reference I&#8217;m using a\u00a0<em>MacBook Pro (Retina, 13-inch, Early 2015), 2.7 GHz Intel Core i5 Processor and 8 GB 1867 MHz DDR3 of Memory.<\/em><\/p>\n<p>If we wanted to see a graph of this profile we need to run the web command like this:<\/p>\n<pre class=\"lang:default decode:true \">(pprof) web\r\n(pprof)<\/pre>\n<p>This will open up your default browser and display an image of the profile. Here&#8217;s a crop of the image that concerns our Fibonacci function:<\/p>\n<p><img decoding=\"async\" class=\"alignnone wp-image-2795\" src=\"https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/exponential-300x222.png\" alt=\"golang profiling\" width=\"573\" height=\"424\" srcset=\"https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/exponential-300x222.png 300w, https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/exponential-768x568.png 768w, https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/exponential-1024x758.png 1024w, https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/exponential.png 1654w\" sizes=\"(max-width: 573px) 100vw, 573px\" \/><\/p>\n<p>So during that profile, 60.97% of the time, the <span class=\"lang:default decode:true crayon-inline \">exponentialFibonacci<\/span>\u00a0was running on the CPU.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Optimizations\"><\/span>Optimizations<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now, we know from the theory that O(n) &lt; O(2^n). Let&#8217;s see if this holds up in practice, it we were to replace the <span class=\"lang:default decode:true crayon-inline \">exponentialFibonacci<\/span>\u00a0 call with <span class=\"lang:default decode:true crayon-inline \">linearFibonacci<\/span>\u00a0 inside the <span class=\"lang:default decode:true crayon-inline \">handler<\/span>\u00a0 function.<\/p>\n<p>Now we run the profile again. You can immediately see that it took less time because the benchmark actually finishes really fast this time.<\/p>\n<p>If we run\u00a0<span class=\"lang:default decode:true crayon-inline \">top5<\/span>\u00a0now, the <span class=\"lang:default decode:true crayon-inline \">linearFibonacci<\/span>\u00a0 function doesn&#8217;t even make the cut. \u00a0Even if you try to do\u00a0<span class=\"lang:default decode:true crayon-inline \">top100<\/span>\u00a0you will not find it because the compiler inlined that particular code.<\/p>\n<p>So we need to rebuild the application with the compiler flags that disable inlining like this:<\/p>\n<p><span class=\"lang:default decode:true crayon-inline \">go build -gcflags -l -o myserver main.go<\/span><\/p>\n<p>&nbsp;<\/p>\n<p>Now even with this flag enabled I had a hard time finding the function in the top. I went ahead and increased the hard-coded value for the n-th Fibonacci number to 10,000. So I&#8217;m looking for the 10,000th Fibonacci number, this number doesn&#8217;t even fit inside the integer datatype in Golang. It will overflow several times before coming to a stop. I also increased the benchmark to 1,000,000 requests.<\/p>\n<p>Now if I run <span class=\"lang:default decode:true crayon-inline\">top5<\/span>\u00a0I get:<\/p>\n<pre class=\"lang:default decode:true \">(pprof) top5\r\n36.21s of 46.49s total (77.89%)\r\nDropped 226 nodes (cum &lt;= 0.23s)\r\nShowing top 5 nodes out of 102 (cum &gt;= 1.97s)\r\n      flat  flat%   sum%        cum   cum%\r\n    25.94s 55.80% 55.80%     26.23s 56.42%  syscall.Syscall\r\n     3.38s  7.27% 63.07%      3.38s  7.27%  runtime.kevent\r\n     2.60s  5.59% 68.66%      2.60s  5.59%  runtime.usleep\r\n     2.32s  4.99% 73.65%      4.26s  9.16%  main.linearFibonacci\r\n     1.97s  4.24% 77.89%      1.97s  4.24%  runtime.mach_semaphore_signal<\/pre>\n<p>Or in graphical format:<\/p>\n<p><img decoding=\"async\" class=\"alignnone wp-image-2796\" src=\"https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/linear-300x193.png\" alt=\"golang profiling\" width=\"578\" height=\"372\" srcset=\"https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/linear-300x193.png 300w, https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/linear-768x494.png 768w, https:\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/08\/linear.png 952w\" sizes=\"(max-width: 578px) 100vw, 578px\" \/><\/p>\n<p>As you can see, it barely even makes a dent.<\/p>\n<p>So for this test, <strong>calculating the 25th Fibonacci number recursively takes 60% of the CPU while calculating the 10,000th Fibonacci number linearly takes 4% of the CPU (without inlining).<\/strong><\/p>\n<p>Another useful command for pprof to see how much CPU time a function takes is the <span class=\"lang:default decode:true crayon-inline \">list<\/span>\u00a0 command. Or, if you&#8217;re like me, to find out if a function is actually called.<\/p>\n<p>For our <span class=\"lang:default decode:true crayon-inline \">linearFibonacci<\/span>\u00a0 function it looks like this:<\/p>\n<pre class=\"lang:default decode:true \">(pprof) list linearFibonacci\r\nTotal: 46.49s\r\nROUTINE ======================== main.linearFibonacci in \/Users\/username\/workspace\/go\/src\/github.com\/username\/test_profiling\/main.go\r\n     2.32s      4.26s (flat, cum)  9.16% of Total\r\n         .          .      8:)\r\n         .          .      9:\r\n         .          .     10:\/\/ O(n) Fibonacci\r\n         .          .     11:func linearFibonacci(n int) int {\r\n         .          .     12:\t\/\/ Create an int array of size n + 1\r\n      10ms      1.95s     13:\tv := make([]int, n+1)\r\n         .          .     14:\r\n         .          .     15:\t\/\/ F(0) = 0\r\n         .          .     16:\tv[0] = 0\r\n         .          .     17:\t\/\/ F(1) = 1\r\n         .          .     18:\tv[1] = 1\r\n         .          .     19:\r\n         .          .     20:\t\/\/ F(i) = F(i-1) + F(i-2)\r\n     260ms      260ms     21:\tfor i := 2; i &lt;= n; i++ {\r\n     2.05s      2.05s     22:\t\tv[i] = v[i-1] + v[i-2]\r\n         .          .     23:\t}\r\n         .          .     24:\r\n         .          .     25:\t\/\/ F(n) - return the n-th Fibonacci number\r\n         .          .     26:\treturn v[n]\r\n         .          .     27:}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"A_better_comparison\"><\/span>A better comparison<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A better way to compare the two methods, and the theory to practice, is this:<\/p>\n<ol>\n<li>Knowing that the exponentialFibonacci method is O(2^n), it would take approximately 2^25 =\u00a033554432 instructions to calculate the 25th Fibonacci number.<\/li>\n<li>Linearly, calculating the\u00a033554432th Fibonacci number should take roughly the same time as calculating the 25th number exponentially.<\/li>\n<\/ol>\n<p>So following the methodology above we do this:<\/p>\n<ol>\n<li>Build the application using the exponentialFibonacci(25) call.<\/li>\n<li>Start the application.<\/li>\n<li>Start the Apache Benchmark for 1,000,000 requests.<\/li>\n<li>Start the CPU profile for 30s seconds.<\/li>\n<\/ol>\n<p>We get this:<\/p>\n<pre class=\"lang:default decode:true\">(pprof) top5\r\n98.27s of 99.02s total (99.24%)\r\nDropped 64 nodes (cum &lt;= 0.50s)\r\nShowing top 5 nodes out of 30 (cum &gt;= 1.30s)\r\n      flat  flat%   sum%        cum   cum%\r\n    60.78s 61.38% 61.38%     60.78s 61.38%  main.exponentialFibonacci\r\n    24.54s 24.78% 86.16%     24.54s 24.78%  fmt.(*pp).doPrintf\r\n    12.95s 13.08% 99.24%     12.95s 13.08%  syscall.Syscall\r\n         0     0% 99.24%      1.30s  1.31%  bufio.(*Reader).ReadLine\r\n         0     0% 99.24%      1.30s  1.31%  bufio.(*Reader).ReadSlice<\/pre>\n<p>Now for the second part:<\/p>\n<ol>\n<li>Build the application using the linearFibonacci(33554432) call.<\/li>\n<li>Start the application.<\/li>\n<li>Start the Apache Benchmark for 1,000,000 requests.<\/li>\n<li>Start the CPU profile for 30s seconds.<\/li>\n<\/ol>\n<p>We get this:<\/p>\n<pre class=\"lang:default decode:true\">(pprof) top5\r\n49280ms of 49870ms total (98.82%)\r\nDropped 92 nodes (cum &lt;= 249.35ms)\r\nShowing top 5 nodes out of 29 (cum &gt;= 470ms)\r\n      flat  flat%   sum%        cum   cum%\r\n   28650ms 57.45% 57.45%    44400ms 89.03%  main.linearFibonacci\r\n   15660ms 31.40% 88.85%    15660ms 31.40%  runtime.memclr\r\n    3910ms  7.84% 96.69%     3910ms  7.84%  runtime.usleep\r\n     590ms  1.18% 97.87%      590ms  1.18%  runtime.duffcopy\r\n     470ms  0.94% 98.82%      470ms  0.94%  runtime.mach_semaphore_timedwait<\/pre>\n<p>As you can see, the flat percentages, which is how much of the time was spent in the routine itself, is roughly the same. 61.38% vs 57.45%, it&#8217;s about 4% difference between them.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Profiling_memory\"><\/span>Profiling memory<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Using the same process, you can run the following command to profile memory:<\/p>\n<p><span class=\"lang:default decode:true crayon-inline \">go tool pprof -alloc_objects myserver http:\/\/localhost:8080\/debug\/pprof\/heap<\/span><\/p>\n<p>If you run a top command you should see something like this:<\/p>\n<pre class=\"lang:default decode:true\">(pprof) top10\r\n9741685 of 9927382 total (98.13%)\r\nDropped 7 nodes (cum &lt;= 49636)\r\nShowing top 10 nodes out of 33 (cum &gt;= 99079)\r\n      flat  flat%   sum%        cum   cum%\r\n   3182489 32.06% 32.06%    3182489 32.06%  net\/textproto.(*Reader).ReadMIMEHeader\r\n   2050835 20.66% 52.72%    2050835 20.66%  context.WithCancel\r\n   1068043 10.76% 63.47%    8447075 85.09%  net\/http.(*conn).readRequest\r\n    675175  6.80% 70.28%    5155947 51.94%  net\/http.readRequest\r\n    667729  6.73% 77.00%     667729  6.73%  net\/url.parse\r\n    655370  6.60% 83.60%    1414760 14.25%  main.handler\r\n    618866  6.23% 89.84%     618866  6.23%  main.linearFibonacci\r\n    589833  5.94% 95.78%     589833  5.94%  net\/textproto.(*Reader).ReadLine\r\n    134266  1.35% 97.13%     172250  1.74%  net\/http.newBufioWriterSize\r\n     99079     1% 98.13%      99079     1%  sync.(*Pool).pinSlow<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now that you&#8217;ve seen the basics on how to profile your Golang web apps, you can start diving into heavier stuff like <a href=\"https:\/\/blog.golang.org\/profiling-go-programs\">this<\/a>. Take some time and run a profile on your own Golang web apps.<\/p>\n<p>Also, you should see the Gophercon talk I mentioned at the start of this post, it&#8217;s quite good.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve been watching the 2017 Gophercon videos from\u00a0here. There are many good talks that I would recommend watching from that [&hellip;]<\/p>\n","protected":false},"author":28,"featured_media":3288,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[73,78],"tags":[147,198],"yst_prominent_words":[],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2792"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/28"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=2792"}],"version-history":[{"count":4,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2792\/revisions"}],"predecessor-version":[{"id":133366,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2792\/revisions\/133366"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media\/3288"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=2792"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=2792"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=2792"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=2792"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}