{"id":2895,"date":"2019-09-29T18:07:03","date_gmt":"2019-09-29T18:07:03","guid":{"rendered":"https:\/\/nenadnoveljic.com\/blog\/?p=2895"},"modified":"2019-09-29T18:07:05","modified_gmt":"2019-09-29T18:07:05","slug":"enriching-trace-with-call-stacks-gdb","status":"publish","type":"post","link":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/","title":{"rendered":"Enriching Trace with Call Stacks Part 2: gdb"},"content":{"rendered":"<p>In a <a href=\"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks\/\">previous blog post<\/a> I explained how to gather call stacks with DTrace every time a string of interest gets written into a trace file. This technique comes in handy when researching propriatery software internals. I&#8217;ve been using it often to study optimizer; among others, to identify Oracle C functions participating in various query transformations. Unfortunately, DTrace isn&#8217;t always available, so I came up with an alternative approach based on gdb.<\/p>\n<p>Also here, I use the same tactic &#8211; printing the output buffer and backtrace in the intercepted OS kernel function write calls.<\/p>\n<p>Almost always, there will be plenty of write calls, so we have to be very selective about when to stop and capture the information. The write function specification helps us narrowing down the focus:<\/p>\n<pre><code>NAME\n       write, pwrite, writev - write on a file\n\nSYNOPSIS\n       ssize_t write(int <span style=\"color:red\">fildes<\/span>, const void <span style=\"color:blue\">*buf<\/span>, size_t nbyte);\n...\nDESCRIPTION\n       The  write()  function  attempts  to  write nbyte bytes from the buffer\n       pointed to by buf, to the file associated with the open  file  descrip-\n       tor, fildes.\n...\n;<\/code><\/pre>\n<p>The first argument is the file descriptor. It can be retrieved for the traced process with the pfiles utility:<\/p>\n<pre><code>pfiles PID\n...\n  <span style=\"color:red\">15<\/span>: S_IFREG mode:0640 dev:275,65861 ino:3334935 uid:1000 gid:1000 size:112333 (110KB)\n      O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE FD_CLOEXEC\n      \/u00\/oracle\/orabase\/diag\/rdbms\/db_site1\/DB\/trace\/DB_ora_16204.trc\n      offset:112333 (110KB)\n...<\/code><\/pre>\n<p>As you can see, the file descriptor for the dedicated server process&#8217; trace file is <span style=\"color:red\">15<\/span>. (I switched the optimizer tracing on prior to executing pfiles.) The second argument is the buffer address which contains string to be written. Since the first two arguments are passed via the CPU registers <span style=\"color:red\">rdi<\/span> and <span style=\"color:blue\">rsi<\/span>, we can set a conditional breakpoint as follows:<\/p>\n<pre><code>b *write if <span style=\"color:red\">$rdi == 15<\/span> &amp;&amp; strstr(<span style=\"color:blue\">$rsi,\"JPPD\"<\/span>)<\/code><\/pre>\n<p>The program execution will stop when the string <span style=\"color:blue\">JPPD<\/span> (Join Predicate Push-Down) is to be written into the trace file.<\/p>\n<p>The following breakpoint commands print out the buffer content and call stack before continuing with the further execution.<\/p>\n<pre><code>commands 1\nsilent\nx\/s <span style=\"color:blue\">$rsi<\/span>\nbt\ncontinue\nend<\/code><\/pre>\n<p>Also, I usually configure resuming on received signal (unwindonsignal on), logging to file as well as disable the interactive mode (pagination off).<\/p>\n<pre><code>attach PID\n\nset unwindonsignal on\n\nset pagination off\n\nset logging file stacks5.log\nset logging on\n\nc\n\nset logging off<\/code><\/pre>\n<p>Find below an example of a stack captured during a JPPD transformation:<\/p>\n<pre><code>0x7fffbfff1990: \"<span style=\"color:blue\">JPPD<\/span>:   Checking validity of push-down from query block MAIN (#1) to query block RECEIVER (#2)\\n\"\n#0  0x00007ffc3083d0c0 in write () from \/lib\/64\/libc.so.1\n#1  0x000000000b181d82 in sdbgrfuwf_write_file ()\n#2  0x000000000b181cea in sdbgrfwf_write_file ()\n#3  0x000000000b165ae0 in dbgtfdFileWrite ()\n#4  0x000000000b0d1527 in dbgtfdFileAccessCbk ()\n#5  0x000000000b163365 in dbgtfWriteRec ()\n#6  0x000000001d6a375a in dbgtRecVAWriteDisk ()\n#7  0x000000001d699e52 in dbgtGrpBVaList_int ()\n#8  0x000000000b37ef1d in dbgtGrpB_int ()\n#9  0x0000000019a38fb1 in <span style=\"color:blue\">kkqjpdcvvpd<\/span> ()\n#10 0x000000000a775125 in <span style=\"color:blue\">kkqjpdctr<\/span> ()\n#11 0x000000000a47ba33 in qksqbApplyToQbcLoc ()\n#12 0x000000000a47b6e4 in qksqbApplyToQbc ()\n#13 0x000000000a478d82 in kkqctdrvTD ()\n#14 0x000000000a774544 in <span style=\"color:blue\">kkqjpddrv<\/span> ()\n#15 0x000000000a471250 in kkqdrv ()\n#16 0x000000000a46e6a0 in kkqctdrvIT ()\n#17 0x000000000a25e5bb in apadrv ()\n#18 0x000000000a2583fa in opitca ()\n#19 0x000000000a25796c in kksFullTypeCheck ()\n#20 0x0000000009d2fbdc in rpiswu2 ()\n#21 0x000000000a469bc6 in kksLoadChild ()\n#22 0x000000000a24c25d in kxsGetRuntimeLock ()\n#23 0x000000000a240ad0 in kksfbc ()\n#24 0x000000000a463ee0 in kkspsc0 ()\n#25 0x000000000a2ddd17 in kksParseCursor ()\n#26 0x000000000a2d9876 in opiosq0 ()\n#27 0x000000000a4636d7 in kpooprx ()\n#28 0x000000000a393c25 in kpoal8 ()\n#29 0x0000000009d08cee in opiodr ()\n#30 0x0000000009d120d2 in ttcpip ()\n#31 0x0000000009d0e318 in opitsk ()\n#32 0x0000000009d0cf64 in opiino ()\n#33 0x0000000009d08cee in opiodr ()\n#34 0x0000000009d082b2 in opidrv ()\n#35 0x0000000009d07f07 in sou2o ()\n#36 0x0000000009d07d4e in opimai_real ()\n#37 0x0000000009d07866 in ssthrdmain ()\n#38 0x0000000009d075d7 in main ()<\/code><\/pre>\n<p>If you&#8217;d like to gather call stacks related to, for instance, the OR-Expansion (<span style=\"color:blue\">ORE<\/span>), you can just change the breakpoint condition:<\/p>\n<pre><code>cond 1 $rdi == 15 &amp;&amp; strstr(<span style=\"color:blue\">$rsi,\"ORE:\"<\/span>)<\/code><\/pre>\n<p>Here&#8217;s an example of a captured call stack:<\/p>\n<pre><code>0x7fffbfff4c00: \"<span style=\"color:blue\">ORE<\/span>: after OR Expansion:******* UNPARSED QUERY IS *******\\n\"\n#0  0x00007ffc3083d0c0 in write () from \/lib\/64\/libc.so.1\n#1  0x000000000b181d82 in sdbgrfuwf_write_file ()\n#2  0x000000000b181cea in sdbgrfwf_write_file ()\n#3  0x000000000b165ae0 in dbgtfdFileWrite ()\n#4  0x000000000b0d1527 in dbgtfdFileAccessCbk ()\n#5  0x000000000b188891 in dbgtfPutStr ()\n#6  0x000000000b18806d in dbktPri ()\n#7  0x000000000b2125e3 in ksdwrf ()\n#8  0x000000001562296d in msqPrintQuery ()\n#9  0x0000000010d4d986 in qksqbDumpAsSQL ()\n#10 0x0000000019b48fb3 in <span style=\"color:blue\">kkqoreBuildUA<\/span> ()\n#11 0x0000000019b48231 in <span style=\"color:blue\">kkqoreTransform<\/span> ()\n#12 0x0000000019b40fbf in <span style=\"color:blue\">kkqoregdtr<\/span> ()\n#13 0x000000000a47ba33 in qksqbApplyToQbcLoc ()\n#14 0x000000000a47b6e4 in qksqbApplyToQbc ()\n#15 0x0000000019b40ecf in <span style=\"color:blue\">kkqoredrv<\/span> ()\n#16 0x000000000a470232 in kkqdrv ()\n#17 0x000000000a46e6a0 in kkqctdrvIT ()\n#18 0x000000000a25e5bb in apadrv ()\n#19 0x000000000a2583fa in opitca ()\n#20 0x000000000a46907b in kksLoadChild ()\n#21 0x000000000a24c25d in kxsGetRuntimeLock ()\n#22 0x000000000a240ad0 in kksfbc ()\n#23 0x000000000a463ee0 in kkspsc0 ()\n#24 0x000000000a2ddd17 in kksParseCursor ()\n#25 0x000000000a2d9876 in opiosq0 ()\n#26 0x000000000a4636d7 in kpooprx ()\n#27 0x000000000a393c25 in kpoal8 ()\n#28 0x0000000009d08cee in opiodr ()\n#29 0x0000000009d120d2 in ttcpip ()\n#30 0x0000000009d0e318 in opitsk ()\n#31 0x0000000009d0cf64 in opiino ()\n#32 0x0000000009d08cee in opiodr ()\n#33 0x0000000009d082b2 in opidrv ()\n#34 0x0000000009d07f07 in sou2o ()\n#35 0x0000000009d07d4e in opimai_real ()\n#36 0x0000000009d07866 in ssthrdmain ()\n#37 0x0000000009d075d7 in main ()<\/code><\/pre>\n<p>Last but not least, a traced process normally executes many write calls. As a consequence, breakpoint handling will inevitably introduce an enormous overhead. For example, a 10053-traced optimization taking 5s to complete lasted almost 1.5 minutes after setting a breakpoint on write. Therefore, use this technique prudently &#8211; that is, only for experimenting on your sandbox databases. Interestingely, tracing with DTrace is much lighter &#8211; the same optimization lasted &#8220;only&#8221; 12s.<\/p>\n<p>By the way, below are the links to case studies where I used gdb for researching Oracle database internals:<\/p>\n<ul>\n<li><a href=\"https:\/\/nenadnoveljic.com\/blog\/io-sort-cost-calculation\/\">IO Sort Cost Calculation<\/a><\/li>\n<li><a href=\"https:\/\/nenadnoveljic.com\/blog\/memory-leak-parsing\/\">Memory Leak During Parsing<\/a><\/li>\n<li><a href=\"https:\/\/nenadnoveljic.com\/blog\/tracking-the-optimizer-with-gdb\/ \">Tracking the Optimizer with gdb<\/a><\/li>\n<li><a href=\"https:\/\/nenadnoveljic.com\/blog\/tracking-arguments-gdb\/\">Tracking Arguments with gdb<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>How to use gdb for capturing call stacks when trace lines are written <a href=\"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/\" class=\"more-link\">Continue Reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[27,5],"tags":[],"class_list":["post-2895","post","type-post","status-publish","format-standard","hentry","category-gdb","category-oracle"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Enriching Trace with Call Stacks Part 2: gdb - All-round Database Topics<\/title>\n<meta name=\"description\" content=\"How to use gdb for capturing call stacks when trace lines are written\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Enriching Trace with Call Stacks Part 2: gdb - All-round Database Topics\" \/>\n<meta property=\"og:description\" content=\"How to use gdb for capturing call stacks when trace lines are written\" \/>\n<meta property=\"og:url\" content=\"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/\" \/>\n<meta property=\"og:site_name\" content=\"All-round Database Topics\" \/>\n<meta property=\"article:published_time\" content=\"2019-09-29T18:07:03+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-09-29T18:07:05+00:00\" \/>\n<meta name=\"author\" content=\"Nenad Noveljic\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@NenadNoveljic\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Nenad Noveljic\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/\"},\"author\":{\"name\":\"Nenad Noveljic\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#\\\/schema\\\/person\\\/51458d9dd86dbbdd19f5add451d44efa\"},\"headline\":\"Enriching Trace with Call Stacks Part 2: gdb\",\"datePublished\":\"2019-09-29T18:07:03+00:00\",\"dateModified\":\"2019-09-29T18:07:05+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/\"},\"wordCount\":425,\"commentCount\":1,\"articleSection\":[\"gdb\",\"Oracle\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/\",\"url\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/\",\"name\":\"Enriching Trace with Call Stacks Part 2: gdb - All-round Database Topics\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#website\"},\"datePublished\":\"2019-09-29T18:07:03+00:00\",\"dateModified\":\"2019-09-29T18:07:05+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#\\\/schema\\\/person\\\/51458d9dd86dbbdd19f5add451d44efa\"},\"description\":\"How to use gdb for capturing call stacks when trace lines are written\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/enriching-trace-with-call-stacks-gdb\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Enriching Trace with Call Stacks Part 2: gdb\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/\",\"name\":\"All-round Database Topics\",\"description\":\"Nenad Noveljic\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/#\\\/schema\\\/person\\\/51458d9dd86dbbdd19f5add451d44efa\",\"name\":\"Nenad Noveljic\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g\",\"caption\":\"Nenad Noveljic\"},\"sameAs\":[\"nenad-noveljic-9b746a6\",\"https:\\\/\\\/x.com\\\/NenadNoveljic\"],\"url\":\"https:\\\/\\\/nenadnoveljic.com\\\/blog\\\/author\\\/nenad\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Enriching Trace with Call Stacks Part 2: gdb - All-round Database Topics","description":"How to use gdb for capturing call stacks when trace lines are written","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/","og_locale":"en_US","og_type":"article","og_title":"Enriching Trace with Call Stacks Part 2: gdb - All-round Database Topics","og_description":"How to use gdb for capturing call stacks when trace lines are written","og_url":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/","og_site_name":"All-round Database Topics","article_published_time":"2019-09-29T18:07:03+00:00","article_modified_time":"2019-09-29T18:07:05+00:00","author":"Nenad Noveljic","twitter_card":"summary_large_image","twitter_creator":"@NenadNoveljic","twitter_misc":{"Written by":"Nenad Noveljic","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/#article","isPartOf":{"@id":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/"},"author":{"name":"Nenad Noveljic","@id":"https:\/\/nenadnoveljic.com\/blog\/#\/schema\/person\/51458d9dd86dbbdd19f5add451d44efa"},"headline":"Enriching Trace with Call Stacks Part 2: gdb","datePublished":"2019-09-29T18:07:03+00:00","dateModified":"2019-09-29T18:07:05+00:00","mainEntityOfPage":{"@id":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/"},"wordCount":425,"commentCount":1,"articleSection":["gdb","Oracle"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/","url":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/","name":"Enriching Trace with Call Stacks Part 2: gdb - All-round Database Topics","isPartOf":{"@id":"https:\/\/nenadnoveljic.com\/blog\/#website"},"datePublished":"2019-09-29T18:07:03+00:00","dateModified":"2019-09-29T18:07:05+00:00","author":{"@id":"https:\/\/nenadnoveljic.com\/blog\/#\/schema\/person\/51458d9dd86dbbdd19f5add451d44efa"},"description":"How to use gdb for capturing call stacks when trace lines are written","breadcrumb":{"@id":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/nenadnoveljic.com\/blog\/enriching-trace-with-call-stacks-gdb\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/nenadnoveljic.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Enriching Trace with Call Stacks Part 2: gdb"}]},{"@type":"WebSite","@id":"https:\/\/nenadnoveljic.com\/blog\/#website","url":"https:\/\/nenadnoveljic.com\/blog\/","name":"All-round Database Topics","description":"Nenad Noveljic","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/nenadnoveljic.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/nenadnoveljic.com\/blog\/#\/schema\/person\/51458d9dd86dbbdd19f5add451d44efa","name":"Nenad Noveljic","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a97b796613ea48ec8a7b79c8ffe1c685dcffc920c68121f6238d5caab5070670?s=96&d=mm&r=g","caption":"Nenad Noveljic"},"sameAs":["nenad-noveljic-9b746a6","https:\/\/x.com\/NenadNoveljic"],"url":"https:\/\/nenadnoveljic.com\/blog\/author\/nenad\/"}]}},"_links":{"self":[{"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/posts\/2895","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/comments?post=2895"}],"version-history":[{"count":1,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/posts\/2895\/revisions"}],"predecessor-version":[{"id":2960,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/posts\/2895\/revisions\/2960"}],"wp:attachment":[{"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/media?parent=2895"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/categories?post=2895"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nenadnoveljic.com\/blog\/wp-json\/wp\/v2\/tags?post=2895"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}