{"id":2325,"date":"2020-03-13T18:52:54","date_gmt":"2020-03-13T18:52:54","guid":{"rendered":"http:\/\/dlang.org\/blog\/?p=2325"},"modified":"2021-09-30T13:36:57","modified_gmt":"2021-09-30T13:36:57","slug":"tracing-d-applications","status":"publish","type":"post","link":"https:\/\/dlang.org\/blog\/2020\/03\/13\/tracing-d-applications\/","title":{"rendered":"Tracing D Applications"},"content":{"rendered":"<p><img loading=\"lazy\" class=\"alignleft size-full wp-image-1345\" src=\"http:\/\/dlang.org\/blog\/wp-content\/uploads\/2018\/02\/bug.jpg\" alt=\"\" width=\"256\" height=\"156\" \/>At one time or another during application development you need to make a decision: does your application work like it should and, if not, what is wrong with it? There are different techniques to help you decide, some of which are logging, tracing, and profiling. How are they different? One way to look at it is like this:<\/p>\n<ul>\n<li>when you know exactly the events you are interested in to make the decision, you use logging<\/li>\n<li>when you don\u2019t know exactly the events you need to make a decision and you are forced to collect as many events as you can, you use tracing<\/li>\n<li>when you need to collect some events and analyze them to derive new information, you use profiling<\/li>\n<\/ul>\n<p>In this article, we focus on tracing.<\/p>\n<p>When developing an application, you can use tracing to monitor its characteristics at run time to, for example, estimate its performance or memory consumption. There are several options to do so, and some of them are:<\/p>\n<ul>\n<li>means provided by the programming language (for example, using D\u2019s <code>writef<\/code>, a.k.a. <code>printf<\/code> debugging)<\/li>\n<li>debuggers (using scripts or remote tracing)<\/li>\n<li>OS-specific tracing frameworks (linux {k|u}probes and usdt probes, linux kernel event, performance events in windows etc)<\/li>\n<\/ul>\n<p>In this article, the following contrived D example is used to help illustrate all three cases. We\u2019ll be focusing on Linux. All example code in this article can be found in the GitHub repository at <a href=\"https:\/\/github.com\/drug007\/tracing_post\">https:\/\/github.com\/drug007\/tracing_post<\/a>.<\/p>\n<pre class=\"prettyprint lang-d\">foreach(counter; 0..total_cycles)\r\n{\r\n    \/\/ randomly generate one of three kinds of event\r\n    Event event = cast(Event) uniform(0, 3);\r\n\r\n    \/\/ \"perform\" the job and benchmark its CPU execution time\r\n    switch (event)\r\n    {\r\n        case Event.One:\r\n\r\n            doSomeWork;\r\n\r\n        break;\r\n        case Event.Two:\r\n\r\n            doSomeWork;\r\n\r\n        break;\r\n        case Event.Three:\r\n\r\n            doSomeWork;\r\n\r\n        break;\r\n        default:\r\n            assert(0);\r\n    }\r\n}<\/pre>\n<p><code>doSomeWork<\/code> simulates a CPU-intensive job by using DRuntime\u2019s <code>Thread.sleep<\/code> method. This is a very common pattern where an application runs in cycles and, on every iteration, performs a job depending on the application state. Here we can see that the application has three code paths (<code>CaseOne<\/code>, <code>CaseTwo<\/code>, and <code>CaseThree<\/code>). We need to trace the application at run time and collect information about its timings.<\/p>\n<h2 id=\"thewritef-basedapproach\">The <code>writef<\/code>-Based Approach<\/h2>\n<p>Using <code>writef\/ln<\/code> from Phobos, <a href=\"https:\/\/dlang.org\/phobos\/index.html\">D\u2019s standard library<\/a>, to trace the application is naive, but can be very useful nevertheless. The code <a href=\"https:\/\/github.com\/drug007\/tracing_post\/blob\/master\/tracing_writef.d\">from tracing_writef.d<\/a>:<\/p>\n<pre class=\"prettyprint lang-d\">    case Event.One:\r\n            auto sw = StopWatch(AutoStart.no);\r\n            sw.start();\r\n\r\n            doSomeWork;\r\n\r\n            sw.stop();\r\n            writefln(\"%d:\\tEvent %s took: %s\", counter, event, sw.peek);\r\n        break;\r\n<\/pre>\n<p>With this trivial approach, <code>StopWatch<\/code> from the standard library is used to measure the execution time of the code block of interest. Compile and run the application with the command <code>dub tracing_writef.d<\/code> and look at its output:<\/p>\n<pre>Running .\/example-writef\r\n0:      Event One took:   584 ms, 53 \u03bcs, and 5 hnsecs\r\n1:      Event One took:   922 ms, 72 \u03bcs, and 6 hnsecs\r\n2:      Event Two took:   1 sec, 191 ms, 73 \u03bcs, and 8 hnsecs\r\n3:      Event Two took:   974 ms, 73 \u03bcs, and 7 hnsecs\r\n...<\/pre>\n<p>There is a price for this\u2014we need to compile tracing code into our binary, we need to manually implement the collection of tracing output, disable it when we need to, and so on\u2014and this means the size of the binary increases. To summarize:<\/p>\n<p><strong>Pros<\/strong><\/p>\n<ul>\n<li>all the might of Phobos is available to employ (<a href=\"https:\/\/dlang.org\/blog\/2017\/08\/23\/d-as-a-better-c\/\">except when in BetterC mode<\/a>)<\/li>\n<li>tracing output can be displayed in a human readable format (look at the nice output of <code>Duration<\/code> above; thanks to Jonathan M. Davis for <a href=\"https:\/\/dlang.org\/phobos\/std_datetime.html\">the <code>std.datetime<\/code> package<\/a>)<\/li>\n<li>source code is portable<\/li>\n<li>easy to use<\/li>\n<li>no third-party tools required<\/li>\n<\/ul>\n<p><strong>Cons<\/strong><\/p>\n<ul>\n<li>the application must be rebuilt and restarted in order to make any changes, which is inappropriate for some applications (such as servers)<\/li>\n<li>no low-level access to the application state<\/li>\n<li>noise in the code due to the addition of tracing code<\/li>\n<li>can be unusable due to a lot of debug output<\/li>\n<li>overhead can be large<\/li>\n<li>can be hard to use in production<\/li>\n<\/ul>\n<p>This approach is very suitable in the early stages of development and less useful in the final product. Although, if the tracing logic is fixed and well defined, this approach can be used in production-ready applications\/libraries. For example, this approach was suggest by Stefan Koch for tracing the DMD frontend to <a href=\"https:\/\/github.com\/dlang\/dmd\/pull\/7792\">profile performance and memory consumption<\/a>.<\/p>\n<h2 id=\"debugger-basedapproach\">Debugger-Based Approach<\/h2>\n<p>The debugger, in this case GDB, is a more advanced means to trace applications. There is no need to modify the application to change the tracing methodology, making it very useful in production. Instead of compiling tracing logic into the application, breakpoints are set. When the debugger stops execution on a breakpoint, the developer can use the large arsenal of GDB functionality to inspect the internal state of the <em>inferior<\/em> (which, in GDB terms, usually refers to <a href=\"https:\/\/sourceware.org\/gdb\/onlinedocs\/gdb\/Inferiors-and-Programs.html\">the process being debugged<\/a>). It is not possible in this case to use Phobos directly, but helpers are available and, moreover, you have access to registers and the stack\u2014options which are unavailable in the case of <code>writef<\/code> debugging.<\/p>\n<p>Let\u2019s take a look <a href=\"https:\/\/github.com\/drug007\/tracing_post\/blob\/master\/tracing_gdb.d\">the code from tracing_gdb.d<\/a> for the first event:<\/p>\n<pre class=\"prettyprint lang-d\">    case Case.One:\r\n\r\n        doSomeWork;\r\n\r\n    break;<\/pre>\n<p>As you can see, now there is no tracing code and it is much cleaner. The tracing logic is placed in <a href=\"https:\/\/github.com\/drug007\/tracing_post\/blob\/master\/trace.gdb\">a separate file called trace.gdb<\/a>. It consists of a series of command blocks configured to execute on specific breakpoints, like this:<\/p>\n<pre>set pagination off\r\nset print address off\r\n\r\nbreak app.d:53\r\ncommands\r\nset $EventOne = currClock()\r\ncontinue\r\nend\r\n\r\nbreak app.d:54\r\ncommands\r\nset $EventOne = currClock() - $EventOne\r\nprintf \"%d:\\tEvent One   took: %s\\n\", counter, printClock($EventOne)\r\ncontinue\r\nend\r\n\r\n...\r\n\r\nrun\r\nquit<\/pre>\n<p>In the first line, pagination is switched off. This enables scrolling so that there is no need to press \u201cEnter\u201d or \u201cQ\u201d to continue script execution when the current console fills up. The second line disables showing the address of the current breakpoint in order to make the output less verbose. Then breakpoints are set on lines 53 and 54, each followed by a list of commands (between the <code>commands<\/code> and <code>end<\/code> labels) that will be executed when GDB stops on these breakpoints. The breakpoint on line 53 is configured to fetch the current timestamp (using a helper) before <code>doSomeWork<\/code> is called, and the one on line 54 to get the current timestamp after <code>doSomeWork<\/code> has been executed. In fact, line 54 is an empty line in the source code, but GDB is smart enough to set the breakpoint on the next available line. <code>$EventOne<\/code> is <a href=\"https:\/\/www.sourceware.org\/gdb\/onlinedocs\/gdb\/Convenience-Vars.html\">a convenience variable<\/a> where we store the timestamps to calculate code execution time. <code>currClock()<\/code> and <code>printClock(long)<\/code> are helpers to let us prettify the formatting by means of Phobos. The last two commands in the script initiate the debugging and quit the debugger when it\u2019s finished.<\/p>\n<p>To build and run this tracing session, use the following commands:<\/p>\n<pre class=\"prettyprint lang-bash\">dub build tracing_gdb.d --single\r\ngdb --command=trace.gdb .\/tracing-gdb | grep Event<\/pre>\n<p><code>trace.gdb<\/code> is the name of the GDB script and <code>tracing-gdb<\/code> is the name of the binary. We use <code>grep<\/code> to make the GDB output look like <code>writefln<\/code> output for easier comparison.<\/p>\n<p><strong>Pros<\/strong><\/p>\n<ul>\n<li>the code is clean and contains no tracing code<\/li>\n<li>there is no need to recompile the application to change the tracing methodology\u2014in many cases, it\u2019s enough to simply change the GDB script<\/li>\n<li>there is no need to restart the application<\/li>\n<li>it can be used in production (sort of)<\/li>\n<li>there is no overhead if\/when not tracing and little when tracing<\/li>\n<li><a href=\"https:\/\/www.sourceware.org\/gdb\/onlinedocs\/gdb\/Set-Watchpoints.html#Set-Watchpoints\">watchpoints<\/a> and <a href=\"https:\/\/www.sourceware.org\/gdb\/onlinedocs\/gdb\/Set-Catchpoints.html#Set-Catchpoints\">catchpoints<\/a> can be used instead of breakpoints<\/li>\n<\/ul>\n<p><strong>Cons<\/strong><\/p>\n<ul>\n<li>using breakpoints in some cases may be inconvenient, annoying, or impossible.<\/li>\n<li>GDB\u2019s pretty-printing provides \u201cless pretty\u201d output because of the lack of full Phobos support compared to the <code>writef<\/code> approach<\/li>\n<li>sometimes GDB is not available in production<\/li>\n<\/ul>\n<p>The point about setting breakpoints in GDB being inconvenient is based on the fact that you can use only an address, a line number, or a function name (<a href=\"https:\/\/www.sourceware.org\/gdb\/onlinedocs\/gdb\/Set-Breaks.html#Set-Breaks\">see the gdb manual<\/a>). Using an address is too low level and inconvenient. Line numbers are ephemeral and can easily change when the source file is edited, so the scripts will be broken (this is annoying). Using function names is convenient and stable, but is useless if you need to place a tracing probe inside a function.<\/p>\n<p>A good example of using GDB for tracing is <a href=\"https:\/\/github.com\/CyberShadow\/dmdprof\">Vladimir Panteleev\u2019s dmdprof<\/a>.<\/p>\n<h2 id=\"theusdt-basedapproach\">The USDT-Based Approach<\/h2>\n<p>So far we have two ways to trace our application that are complimentary, but is there a way to unify all the advantages of these two approaches and avoid their drawbacks? Of course, the answer is yes. In fact there are several ways to achieve this, but hereafter only one will be discussed: USDT (Userland Statically Defined Tracing).<\/p>\n<p>Unfortunately, due to historical reasons, the Linux tracing ecosystem is fragmented and rather confusing. There is no plain and simple introduction. Get ready to invest much more time if you want to understand this domain. The first well-known, full-fledged tracing framework was DTrace, developed by Sun Microsystems (now it <a href=\"http:\/\/dtrace.org\/blogs\/about\/\">is open source and licensed under the GPL<\/a>). Yes, strace and ltrace have been around for a long time, but they are limited, e.g., they do not let you trace what happens inside a function call. Today, DTrace is available on Solaris, FreeBSD, macOS, and Oracle Linux. DTrace is not available in other Linux distributions because it was initially licensed under the CDDL. In 2018, it was relicensed under the GPL, but by then Linux had its own tracing ecosystem. As with everything, Open Source has disadvantages. In this case, it resulted in fragmentation. There are now several tools\/frameworks\/etc. that are able to solve the same problems in different ways but somehow and sometimes can interoperate with each other.<\/p>\n<p>We will <a href=\"https:\/\/github.com\/iovisor\/bpftrace\">be using bpftrace<\/a>, a high level tracing language <a href=\"http:\/\/www.brendangregg.com\/blog\/2019-01-01\/learn-ebpf-tracing.html\">for Linux eBPF<\/a>. In D, USDT probes are provided by <a href=\"http:\/\/code.dlang.org\/packages\/usdt\">the usdt library<\/a>. Let\u2019s start from <a href=\"https:\/\/github.com\/drug007\/tracing_post\/blob\/master\/tracing_usdt.d\">the code in tracing_usdt.d<\/a>:<\/p>\n<pre class=\"prettyprint lang-d\">\tcase Case.One:\r\n\t\tmixin(USDT_PROBE!(\"dlang\", \"CaseOne\", kind));\r\n\r\n\t\tdoSomeWork;\r\n\r\n\t\tmixin(USDT_PROBE!(\"dlang\", \"CaseOne_return\", kind));\r\n\tbreak;<\/pre>\n<p>Here <a href=\"https:\/\/dlang.org\/spec\/expression.html#mixin_expressions\">we mixed in<\/a> two probes at the start and the end of the code of interest. It looks similar to the first example using <code>writef<\/code>, but a huge difference is that there is no logic here. We only defined two probes that are NOP instructions. That means that these probes have almost zero overhead and we can use them in production. The second great advantage is that we can change the logic while the application is running. That is just impossible when using the <code>writef<\/code> approach. Since we are using bpftrace, we need to write a script, <a href=\"https:\/\/github.com\/drug007\/tracing_post\/blob\/master\/bpftrace.bt\">called bpftrace.bt<\/a>, to define actions that should be performed on the probes:<\/p>\n<pre>usdt:.\/tracing-usdt:dlang:CaseOne\r\n{\r\n\t@last[\"CaseOne\"] = nsecs;\r\n}\r\n\r\nusdt:.\/tracing-usdt:dlang:CaseOne_return\r\n{\r\n\tif (@last[\"CaseOne\"] != 0)\r\n\t{\r\n\t\t$tmp = nsecs;\r\n\t\t$period = ($tmp - @last[\"CaseOne\"]) \/ 1000000;\r\n\t\tprintf(\"%d:\\tEvent CaseOne   took: %d ms\\n\", @counter++, $period);\r\n\t\t@last[\"CaseOne\"] = $tmp;\r\n\t\t@timing = hist($period);\r\n\t}\r\n}\r\n...<\/pre>\n<p>The first statement is the action block. It triggers on the USDT probe that is compiled in the <code>.\/tracing-usdt<\/code> executable (it includes the path to the executable) with the <code>dlang<\/code> provider name and the <code>CaseOne<\/code> probe name. When this probe is hit, then the global (indicated by the <code>@<\/code> sign) associative array <code>last<\/code> updates the current timestamp for its element <code>CaseOne<\/code>. This stores the time of the moment the code starts running. The second action block defines actions performed when the <code>CaseOne_return<\/code> probe is hit. It first checks if corresponding element in the <code>@last<\/code> associative array is already initialized. This is needed because the application may already be running when the script is executed, in which case the <code>CaseOne_return<\/code> probe can be fired before <code>CaseOne<\/code>. Then we calculate how much time code execution took, output it, and store it in a histogram called <code>timing<\/code>.<\/p>\n<p>The BEGIN and END blocks at the top of <code>bpftrace.bt<\/code> define actions that should be performed at the beginning and the end of script execution. This is nothing more than initialization and finalization. Build and run the example with:<\/p>\n<pre class=\"prettyprint lang-bash\">dub build tracing_usdt.d   --single --compiler=ldmd2 # or gdc\r\n.\/tracing-usdt &amp;                                     # run the example in background\r\nsudo bpftrace bpftrace.bt                            # start tracing session<\/pre>\n<p>Output:<\/p>\n<pre>Attaching 8 probes...\r\n0:\tEvent CaseThree took: 552 ms\r\n1:\tEvent CaseThree took: 779 ms\r\n2:\tEvent CaseTwo   took: 958 ms\r\n3:\tEvent CaseOne   took: 1174 ms\r\n4:\tEvent CaseOne   took: 1059 ms\r\n5:\tEvent CaseThree took: 481 ms\r\n6:\tEvent CaseTwo   took: 1044 ms\r\n7:\tEvent CaseThree took: 611 ms\r\n8:\tEvent CaseOne   took: 545 ms\r\n9:\tEvent CaseTwo   took: 1038 ms\r\n10:\tEvent CaseOne   took: 913 ms\r\n11:\tEvent CaseThree took: 989 ms\r\n12:\tEvent CaseOne   took: 1149 ms\r\n13:\tEvent CaseThree took: 541 ms\r\n14:\tEvent CaseTwo   took: 1072 ms\r\n15:\tEvent CaseOne   took: 633 ms\r\n16:\tEvent CaseTwo   took: 832 ms\r\n17:\tEvent CaseTwo   took: 1120 ms\r\n^C\r\n\r\n\r\n\r\n@timing:\r\n[256, 512)             1 |@@@@@                                               |\r\n[512, 1K)             10 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|\r\n[1K, 2K)               7 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                |<\/pre>\n<p>In the session output above there are only 18 lines instead of 20; it\u2019s because <code>tracing-usdt<\/code> was started before the <code>bpftrace<\/code> script so the first two events were lost. Also, it&#8217;s necessary to kill the example by typing <code>Ctrl-C<\/code> after <code>tracing-usdt<\/code> completes. After the <code>bpftrace<\/code> script stops execution, it ouputs the contents of the <code>timing<\/code> map as a histogram. The histogram says that one-time code execution takes between 256 and 512 ms, ten times between 512 and 1024 ms, and seven times more between 1024 and 2048 ms. These builtin statistics make using <code>bpftrace<\/code> easy.<\/p>\n<p><strong>Pros<\/strong><\/p>\n<ul>\n<li>provides low-level access to the code (registers, memory, etc.)<\/li>\n<li>minimal noise in the code<\/li>\n<li>no need to recompile or restart when changing the tracing logic<\/li>\n<li>almost zero overhead<\/li>\n<li>can be effectively used in production<\/li>\n<\/ul>\n<p><strong>Cons<\/strong><\/p>\n<ul>\n<li>learning USDT can be hard, particularly considering the state of the Linux tracing ecosystem<\/li>\n<li>requires external tools (frontends)<\/li>\n<li>OS specific<\/li>\n<\/ul>\n<p>Note: GDB has had support for USDT probes since version 7.5. To use it, modify the <code>trace.gdb<\/code> script to set breakpoints using USDT probes instead of line numbers. That eases development because it eliminates the need to synchronize line numbers during source code modification.<\/p>\n<p>Futher reading:<\/p>\n<ul>\n<li><a href=\"https:\/\/theartofmachinery.com\/2019\/04\/26\/bpftrace_d_gc.html\">Profiling D\u2019s Garbage Collection with Bpftrace<\/a><\/li>\n<li><a href=\"https:\/\/blog.sergiodj.net\/2012\/10\/27\/gdb-and-systemtap-probes-part-2.html\">Systemtap Probes and Gdb<\/a><\/li>\n<\/ul>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<table>\n<colgroup>\n<col \/>\n<col style=\"text-align: center;\" \/>\n<col \/>\n<col \/> <\/colgroup>\n<thead>\n<tr>\n<th>Feature<\/th>\n<th style=\"text-align: center;\">writef<\/th>\n<th>gdb<\/th>\n<th>usdt<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>pretty<br \/>\nprinting<\/td>\n<td style=\"text-align: center;\">by means of Phobos<br \/>\nand other libs<\/td>\n<td>by means of<br \/>\n<a href=\"https:\/\/www.sourceware.org\/gdb\/onlinedocs\/gdb\/Pretty-Printing.html\">pretty-printing<\/a><\/td>\n<td>limited builtins<\/td>\n<\/tr>\n<tr>\n<td>low-level<\/td>\n<td style=\"text-align: center;\">no<\/td>\n<td>yes<\/td>\n<td>yes<\/td>\n<\/tr>\n<tr>\n<td>clean code<\/td>\n<td style=\"text-align: center;\">no<\/td>\n<td>yes<\/td>\n<td>sort of<\/td>\n<\/tr>\n<tr>\n<td>recompilation<\/td>\n<td style=\"text-align: center;\">yes<\/td>\n<td>no<\/td>\n<td>no<\/td>\n<\/tr>\n<tr>\n<td>restart<\/td>\n<td style=\"text-align: center;\">yes<\/td>\n<td>no<\/td>\n<td>no<\/td>\n<\/tr>\n<tr>\n<td>usage<br \/>\ncomplexity<\/td>\n<td style=\"text-align: center;\">easy<\/td>\n<td>easy+<\/td>\n<td>advanced<\/td>\n<\/tr>\n<tr>\n<td>third-party<br \/>\ntools<\/td>\n<td style=\"text-align: center;\">no<\/td>\n<td>only debugger<\/td>\n<td>tracing system front end<\/td>\n<\/tr>\n<tr>\n<td>cross platform<\/td>\n<td style=\"text-align: center;\">yes<\/td>\n<td>sorta of<\/td>\n<td>OS specific<\/td>\n<\/tr>\n<tr>\n<td>overhead<\/td>\n<td style=\"text-align: center;\">can be large<\/td>\n<td>none<\/td>\n<td>can be ignored<br \/>\neven in production<\/td>\n<\/tr>\n<tr>\n<td>production ready<\/td>\n<td style=\"text-align: center;\">sometimes possible<\/td>\n<td>sometimes impossible<\/td>\n<td>yes<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td style=\"text-align: center;\"><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>Feature<\/strong> descriptions:<\/p>\n<ul>\n<li><strong>pretty printing<\/strong> is important if the tracing output should be read by humans (and can be ignored in the case of inter-machine data exchange)<\/li>\n<li><strong>low-level<\/strong> means access to low-level details of the traced binary, e.g., registers or memory<\/li>\n<li><strong>clean code<\/strong> characterizes whether additional tracing code which is unrelated to the applications\u2019s business logic would be required.<\/li>\n<li><strong>recompilation<\/strong> determines if it is necessary to recompile when changing the tracing methodology<\/li>\n<li><strong>restart<\/strong> determines if it is necessary to restart the application when changing the tracing methodology<\/li>\n<li><strong>usage complexity<\/strong> indicates the level of development experience that may be required to utilize this technology<\/li>\n<li><strong>third-party tools<\/strong> describes tools not provided by standard D language distributions are required to use this technology<\/li>\n<li><strong>cross platform<\/strong> indicates if this technology can be used on different platforms without changes<\/li>\n<li><strong>overhead<\/strong> &#8211; the cost of using this technology<\/li>\n<li><strong>production ready<\/strong> &#8211; indicates if this technology may be used in a production system without consequences<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>At one time or another during application development you need to make a decision: does your application work like it should and, if not, what is wrong with it? There are different techniques to help you decide, some of which are logging, tracing, and profiling. How are they different? One way to look at it [&hellip;]<\/p>\n","protected":false},"author":39,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[26,9,30],"tags":[],"_links":{"self":[{"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/posts\/2325"}],"collection":[{"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/users\/39"}],"replies":[{"embeddable":true,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/comments?post=2325"}],"version-history":[{"count":6,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/posts\/2325\/revisions"}],"predecessor-version":[{"id":2351,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/posts\/2325\/revisions\/2351"}],"wp:attachment":[{"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/media?parent=2325"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/categories?post=2325"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dlang.org\/blog\/wp-json\/wp\/v2\/tags?post=2325"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}