o
    iT                     @   s   d dl Z d dlZd dlZd dlZd dlmZ d dlmZ d dlm	Z	 dZ
G dd dZG dd	 d	Zd
ededefddZG dd dZdddZedkrRe  dS dS )    N)reduce)Path)DuckDBPyConnectiona  
:root {
  --text-primary-color: #0d0d0d;
  --text-secondary-color: #444;
  --doc-codebox-border-color: #e6e6e6;
  --doc-codebox-background-color: #f7f7f7;
  --doc-scrollbar-bg: #e6e6e6;
  --doc-scrollbar-slider: #ccc;
  --duckdb-accent: #009982;
  --duckdb-accent-light: #00b89a;
  --card-bg: #fff;
  --border-radius: 8px;
  --shadow: 0 4px 14px rgba(0,0,0,0.05);
}

html, body {
  margin: 0;
  padding: 0;
  font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--text-primary-color);
  background: #fafafa;
  line-height: 1.55;
}

.container {
  max-width: 1000px;
  margin: 40px auto;
  padding: 0 20px;
}

header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 5px;
}

header img {
  width: 100px;
  height: 100px;
}

header h1 {
  font-size: 1.5rem;
  font-weight: 600;
  margin: 0;
  color: var(--text-primary-color);
}

/* === Table Styling (DuckDB documentation style, flat header) === */
table {
  border-collapse: collapse;
  width: 100%;
  margin-bottom: 20px;
  text-align: left;
  font-variant-numeric: tabular-nums;
  border: 1px solid var(--doc-codebox-border-color);
  border-radius: var(--border-radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  background: var(--card-bg);
}

thead {
  background-color: var(--duckdb-accent);
  color: white;
}

th, td {
  padding: 10px 12px;
  font-size: 14px;
  vertical-align: top;
}

th {
  font-weight: 700;
}

tbody tr {
  border-bottom: 1px solid var(--doc-codebox-border-color);
}

tbody tr:last-child td {
  border-bottom: none;
}

tbody tr:hover {
  background: var(--doc-codebox-border-color);
}

tbody tr.phase-details-row {
  border-bottom: none;
}

tbody tr.phase-details-row:hover {
  background: transparent;
}

tbody tr.phase-details-row details summary {
  font-size: 12px;
  padding: 4px 0;
}

tbody tr.phase-details-row details[open] summary {
  margin-bottom: 4px;
}

/* === Chart/Card Section === */
.chart {
  padding: 20px;
  border: 1px solid var(--doc-codebox-border-color);
  border-radius: var(--border-radius);
  background: var(--card-bg);
  box-shadow: var(--shadow);
  overflow: visible;
}

/* === Tree Layout Styling === */
.tf-tree {
  overflow-x: visible;
  overflow-y: visible;
  padding-top: 20px;
}

.tf-nc {
  background: var(--card-bg);
  border: 1px solid var(--doc-codebox-border-color);
  border-radius: var(--border-radius);
  padding: 6px;
  display: inline-block;
}

.node-body {
  font-size: 13px;
  text-align: left;
  padding: 10px;
  white-space: nowrap;
}

.node-body p {
  margin: 2px 0;
}

.node-details {
  white-space: nowrap;
  overflow: visible;
  display: inline-block;
}

/* === Metric Boxes === */
.chart .metrics-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 16px;
  margin-bottom: 20px;
}

.chart .metric-box {
  background: var(--card-bg);
  border: 1px solid var(--doc-codebox-border-color);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow);
  padding: 12px 16px;
  text-align: center;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.chart .metric-box:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}

.chart .metric-title {
  font-size: 13px;
  color: var(--text-secondary-color);
  margin-bottom: 4px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.chart .metric-value {
  font-size: 18px;
  font-weight: 600;
  color: var(--duckdb-accent);
}


/* === SQL Query Block === */
.chart.sql-block {
  background: var(--doc-codebox-background-color);
  border: 1px solid var(--doc-codebox-border-color);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow);
  padding: 16px;
  overflow-x: auto;
  margin-top: 20px;
}

.chart.sql-block pre {
  margin: 0;
  font-family: "JetBrains Mono", "Fira Code", Consolas, monospace;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-primary-color);
  white-space: pre;
}

.chart.sql-block code {
  color: var(--duckdb-accent);
  font-weight: 500;
}


/* === Links, Typography, and Consistency === */
a {
  color: var(--duckdb-accent);
  text-decoration: underline;
  transition: color 0.3s;
}

a:hover {
  color: black;
}

strong {
  font-weight: 600;
}

/* === Dark Mode Support === */
@media (prefers-color-scheme: dark) {
  :root {
    --text-primary-color: #e6e6e6;
    --text-secondary-color: #b3b3b3;
    --doc-codebox-border-color: #2a2a2a;
    --doc-codebox-background-color: #1e1e1e;
    --card-bg: #111;
  }
  body {
    background: #0b0b0b;
  }
  thead {
    background-color: var(--duckdb-accent);
  }
  tbody tr:hover {
    background: #222;
  }
  
  /* Fix tree node text visibility in dark mode */
  .tf-nc .node-body,
  .tf-nc .node-body p,
  .tf-nc .node-details {
    color: #1a1a1a !important;
  }
  
  /* Fix metric title visibility in dark mode */
  .chart .metric-title {
    color: #b3b3b3;
  }
}
c                   @   sB   e Zd ZdedededdfddZdeddfd	d
ZdddZdS )
NodeTimingphasetimedepthreturnNc                 C   s   || _ || _|| _d| _d S Nr   )r   r   r   
percentage)selfr   r   r    r   O/home/ubuntu/.local/lib/python3.10/site-packages/duckdb/query_graph/__main__.py__init__  s   
zNodeTiming.__init__
total_timec                 C   s   | j | | _d S N)r   r   )r   r   r   r   r   calculate_percentage  s   zNodeTiming.calculate_percentagerc                 C   s   | j |j  }t| j|| jS r   )r   r   r   r   )r   r   r   r   r   r   combine_timing  s   zNodeTiming.combine_timing)r   r   r	   r   )	__name__
__module____qualname__strfloatintr   r   r   r   r   r   r   r     s    r   c                   @   sp   e Zd ZdddZdeddfddZdedee fd	d
ZdedefddZ	dee fddZ
defddZdS )
AllTimingsr	   Nc                 C   s
   i | _ d S r   phase_to_timingsr   r   r   r   r   "     
zAllTimings.__init__node_timingc                 C   s4   |j | jv r| j|j  | d S |g| j|j < d S r   )r   r   append)r   r    r   r   r   add_node_timing%  s   zAllTimings.add_node_timingr   c                 C   s
   | j | S r   r   r   r   r   r   r   get_phase_timings+  r   zAllTimings.get_phase_timingsc                 C   s   t tj| j| S r   )r   r   r   r   r#   r   r   r   get_summary_phase_timings.     z$AllTimings.get_summary_phase_timingsc                    s.   t  j }|j fddd |  |S )Nc                    s     | jS r   )r%   r   xr   r   r   <lambda>3  s    z'AllTimings.get_phases.<locals>.<lambda>)key)listr   keyssortreverse)r   phasesr   r   r   
get_phases1  s   zAllTimings.get_phasesc                 C   s$   d}| j D ]
}|| |j7 }q|S r
   )r   r%   r   )r   total_timing_sumr   r   r   r   get_sum_of_all_timings7  s   
z!AllTimings.get_sum_of_all_timingsr	   N)r   r   r   r   r   r"   r   r+   r$   r%   r0   r   r2   r   r   r   r   r   !  s    
r   fpathflagsr	   c                 C   s   t | j|ddS )Nutf8)modeencoding)r   open)r4   r5   r   r   r   	open_utf8>  r&   r:   c                   @   s  e Zd Zd:dedB dedB ddfddZdefddZdefd	d
Zd;dedefddZ	d<de
de
dedefddZededefddZdedededededededefdd Zd!e
dedefd"d#Zd$e
de
de
fd%d&Zed$edefd'd(Zed$edefd)d*Zd$e
defd+d,Zd-edefd.d/Zed$ed0eddfd1d2Zd3ede
ddfd4d5Z	d=d6edB d7edB deddfd8d9ZdS )>ProfilingInfoNconn	from_filer	   c                 C   s   || _ || _d S r   )r<   r=   )r   r<   r=   r   r   r   r   C  s   
zProfilingInfo.__init__c                 C   sL   | j d urt| j d}| W  d    S 1 sw   Y  | jjddS )Nr   json)format)r=   r:   readr<   get_profiling_information)r   fr   r   r   to_jsonG  s
   
 zProfilingInfo.to_jsonc                 C   s   t |  S r   )r>   loadsrC   r   r   r   r   	to_pydictN  s   zProfilingInfo.to_pydictprofile.htmloutput_filec                 C   s   |   }| j||d}|S )N)
input_textrG   )rC   _translate_json_to_html)r   rG   profiling_info_texthtml_outputr   r   r   to_htmlQ  s   zProfilingInfo.to_htmlr   top_nodequery_timingsr   c                 C   sF   t |d t|d |}|| |d D ]}| |||d  qd S )Noperator_typeoperator_timingchildren   )r   r   r"   _get_child_timings)r   rM   rN   r   r    childr   r   r   rS   V  s
   
z ProfilingInfo._get_child_timingsfractionc                 C   s   t dtd| } d}d}t|d |d |d  |   }t|d |d |d  |   }t|d |d |d  |   }d|d|d|dS )zReturns a shade between very light (#f7fff0) and a slightly darker green-yellow,
        depending on the fraction (0..1).
        r   rR   )         )   rW         #02x)maxminr   )rU   light_color
dark_colorr   gbr   r   r   _get_f7fff0_shade_hex\  s      z#ProfilingInfo._get_f7fff0_shade_hexnameresultcpu_timecardestresult_size
extra_infoc                 C   s   d|  t||  d}|dkrdn|dd}	t|d}
d| d	}|d
7 }|d|	 d7 }|dkrU|d|
 d7 }|d| d7 }|d| d7 }|d| d7 }|d7 }|d7 }|d7 }|d| d7 }|d7 }|d7 }|d7 }|d7 }|S )z5Generate the HTML body for a single node in the tree.zbackground-color: ;INVALIDBRIDGE_ .4fz<span class="tf-nc" style="z">z<div class="node-body">z<p><b>z</b></p>r   z	<p>time: zs</p>z<p>cardinality: z</p>z<p>estimate: z<p>result size: z
 bytes</p>z	<details>z<summary>Extra info</summary>z<div class="node-details">z<p></div>z
</details>z</span>)rd   r   replace)r   re   rf   rg   rh   ri   rj   rk   
node_stylenew_nameformatted_numbodyr   r   r   _get_node_bodyn  s(   zProfilingInfo._get_node_body
json_graphc                 C   s   d}d}d}d}|d D ]}|d | }|dkrt |}q|| d| d7 }qtd	d
|}tdd|}| |d |d ||d ||d tdd|}	d}
t|d dkro|
d7 }
|d D ]
}|
| ||7 }
q`|
d7 }
||	 |
 | S )Nz<li>z</li> r   rk   zEstimated Cardinalityz: z <br>z__internal_\s*__zcompress_integral\s*compressrO   rP   operator_cardinalityresult_set_sizez,\s*z, rQ   rR   z<ul>z</ul>)r   resubrx   len_generate_tree_recursive)r   ry   rg   node_prefix_htmlnode_suffix_htmlrk   estimater*   value	node_bodychildren_htmlrT   r   r   r   r     s6   

z&ProfilingInfo._generate_tree_recursive
graph_jsonc                 C   sr  t |}| || d}d}d}| }| }|td|d dg|}|D ]}	||	}
|
| |	dkr?d|	 dn|	}|d| d	t	|
j
d
 d	t|
jd dd  d7 }|	dkr||	}t|dkr|dt| d7 }t|dd ddD ]-}|| d|jd  }|d| d|j dt	|j
d
 dt|jd dd  d	7 }q~|d7 }q)||7 }|| S )z3Generates timing HTML table with expandable phases.z
      <table>
        <thead>
          <tr>
            <th>Phase</th>
            <th>Time (s)</th>
            <th>Percentage</th>
          </tr>
        </thead>z<tbody>z</tbody></table>zExecution Time (CPU)Nz<b>z</b>z
      <tr>
          <td>z</td>
                <td>   d      z%</td>
        </tr>
    rR   z
        <tr class="phase-details-row">
            <td colspan="3">
                <details>
                    <summary style="cursor: pointer; padding: 4px 0; color: var(--text-secondary-color);">
                        Show z nodes
                    </summary>
                    <table style="margin: 8px 0; width: 100%; border: none; box-shadow: none;">
                        <tbody>
    c                 S   s   | j S r   )r   r'   r   r   r   r)     s    z5ProfilingInfo._generate_timing_html.<locals>.<lambda>T)r*   r.   z&nbsp;   z
                            <tr style="background: var(--doc-codebox-background-color);">
                                <td style="padding: 4px 12px; border: none;">u
   ↳ Depth zS</td>
                                <td style="padding: 4px 12px; border: none;">z-%</td>
                            </tr>
    z~
                        </tbody>
                    </table>
                </details>
            </td>
        </tr>
    )r>   rD   _gather_timing_informationr2   r0   r"   r   r%   r   roundr   r   r   r$   r   sortedr   )r   r   rN   ry   
table_head
table_body	table_endexecution_time
all_phasesr   summarized_phasephase_columnphase_timingsr    depth_indentr   r   r   _generate_timing_html  sT   






	




z#ProfilingInfo._generate_timing_htmlc                 C   s   t | }t|ddd|dddkr"t|ddd dnd|dddkr7t|ddd dnd|dddkrLt|ddd dnd|dddkr]|ddd	ndd
}d}|D ]}|d| d||  d7 }qe|d7 }|S )NlatencyN/Arq   total_bytes_readi   @total_bytes_writtensystem_peak_buffer_memorycumulative_rows_scanned,)zExecution Time (s)zTotal GB ReadzTotal GB WrittenzPeak Memory (GB)zRows Scannedz<div class="metrics-grid">zP
            <div class="metric-box">
                <div class="metric-title">z1</div>
                <div class="metric-value">z&</div>
            </div>
            rr   )r>   rD   r   get)r   ry   metricsmetric_grid_htmlr*   r   r   r   _generate_metric_grid_html  s0   
z(ProfilingInfo._generate_metric_grid_htmlc                 C   s&   t | }|dd}d| d}|S )N
query_namer   zx
        <details><summary><b>SQL Query</b></summary>
        <div class="chart sql-block">
            <pre><code>
    zI
            </code></pre>
        </div>
        </details><br>
        )r>   rD   r   )r   ry   	sql_querysql_htmlr   r   r   _generate_sql_query_html  s   
	z&ProfilingInfo._generate_sql_query_htmlc                 C   s>   t |}t|d }d}d}| |d d |}|| | S )Nrg   z&<div class="tf-tree tf-gap-sm"> 
 <ul>z</ul> </div>rQ   r   )r>   rD   r   r   )r   r   ry   rg   tree_prefixtree_suffix	tree_bodyr   r   r   _generate_tree_html!  s   
z!ProfilingInfo._generate_tree_html
json_inputc                 C   sD   ddl m} | |d}|dd|d d|d d	|d
 S )Nr   )HTMLFzW
	${CSS}
	${LIBRARIES}
	<div class="chart" id="query-profile"></div>
	${CHART_SCRIPT}
	z${CSS}cssz${CHART_SCRIPT}chart_scriptz${LIBRARIES}	libraries)IPython.core.displayr   _generate_htmlrs   )r   r   r   rK   r   r   r   _generate_ipython+  s   zProfilingInfo._generate_ipythoninclude_meta_infoc                 C   s   d}d}|t |ddS )NzP<link rel="stylesheet" href="https://unpkg.com/treeflex/dist/css/treeflex.css">
zp<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
rz   )treeflex_css
duckdb_cssr   r   )
qgraph_css)r   r   r   r   r   r   r   _generate_style_html9  s   z"ProfilingInfo._generate_style_htmlr>   c                 C   s   |  |d d | d S )NrQ   r   )rS   )r   r>   rN   r   r   r   r   ?  s   z(ProfilingInfo._gather_timing_information
input_filerH   c                 C   s$  t  }|d ur
|}n&|d ur(t|d}| }W d    n1 s"w   Y  ntd td | |d}| |}| ||}	| |}
| 	|}t|d8}d}|
d|d }|
d	|d
 }|
d|}|
d|}|
d|	}|
d|
}|| W d    d S 1 sw   Y  d S )Nr   z.please provide either input file or input textrR   Tzw+a  <!DOCTYPE html>
    <html>
      <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width">
      <title>Query Profile Graph for Query</title>
      ${TREEFLEX_CSS}
      <style>
        ${DUCKDB_CSS}
        </style>
    </head>
    <body>
        <div class="container">
            <header>
                <img src="https://raw.githubusercontent.com/duckdb/duckdb/refs/heads/main/logo/DuckDB_Logo-horizontal.svg" alt="DuckDB Logo">
                <h1>Query Profile Graph</h1>
            </header>
        <div class="chart" id="query-overview">
            ${METRIC_GRID}
        </div>
      <div class="chart" id="query-profile">
            ${SQL_QUERY}
            ${TIMING_TABLE}
      </div>
      ${TREE}
    </body>
    </html>
    z${TREEFLEX_CSS}r   z${DUCKDB_CSS}r   z${METRIC_GRID}z${SQL_QUERY}z${TIMING_TABLE}z${TREE})r   r:   r@   printexitr   r   r   r   r   rs   write)r   r   rH   rG   rN   textrB   rK   highlight_metric_gridtiming_tabletree_outputsql_query_htmlhtmlr   r   r   rI   E  s2   



"z%ProfilingInfo._translate_json_to_html)NN)rF   )r   )NNrF   )r   r   r   r   r   r   rC   dictrE   rL   objectr   rS   staticmethodr   rd   rx   r   r   r   r   r   r   boolr   r   rI   r   r   r   r   r;   B  s\     
$H
r;   c                  C   s   t jddd} | jddd | jdddd	 | jd
dddd |  }|j}|j}|js@d|v r7|dd}ntd td nd|jv rI|j}ntd td |j	}t
|d}|j|d |rqtj	dt|  dd d S d S )NzQuery Graph GeneratorzjGiven a json profile output, generate a html file showing the query graph and
        timings of operators)progdescriptionz--profile_inputzprofile input in json)helpz--outF)requireddefaultz--open
store_trueT)r   actionr   z.jsonz.htmlz%please provide profile output in jsonrR   z/please provide valid .html file for output name)r=   )rG   zfile://r[   )new)argparseArgumentParseradd_argument
parse_argsprofile_inputoutrs   r   r   r9   r;   rL   
webbrowserr   resolve)parserargsinputoutputopen_outputprofiling_infor   r   r   main}  s2   


 r   __main__r3   )r   r>   r   r   	functoolsr   pathlibr   duckdbr   r   r   r   r   r   r:   r;   r   r   r   r   r   r   <module>   s(        
="
