1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * instrument.c |
4 | * functions for instrumentation of plan execution |
5 | * |
6 | * |
7 | * Copyright (c) 2001-2019, PostgreSQL Global Development Group |
8 | * |
9 | * IDENTIFICATION |
10 | * src/backend/executor/instrument.c |
11 | * |
12 | *------------------------------------------------------------------------- |
13 | */ |
14 | #include "postgres.h" |
15 | |
16 | #include <unistd.h> |
17 | |
18 | #include "executor/instrument.h" |
19 | |
20 | BufferUsage pgBufferUsage; |
21 | static BufferUsage save_pgBufferUsage; |
22 | |
23 | static void BufferUsageAdd(BufferUsage *dst, const BufferUsage *add); |
24 | static void BufferUsageAccumDiff(BufferUsage *dst, |
25 | const BufferUsage *add, const BufferUsage *sub); |
26 | |
27 | |
28 | /* Allocate new instrumentation structure(s) */ |
29 | Instrumentation * |
30 | InstrAlloc(int n, int instrument_options) |
31 | { |
32 | Instrumentation *instr; |
33 | |
34 | /* initialize all fields to zeroes, then modify as needed */ |
35 | instr = palloc0(n * sizeof(Instrumentation)); |
36 | if (instrument_options & (INSTRUMENT_BUFFERS | INSTRUMENT_TIMER)) |
37 | { |
38 | bool need_buffers = (instrument_options & INSTRUMENT_BUFFERS) != 0; |
39 | bool need_timer = (instrument_options & INSTRUMENT_TIMER) != 0; |
40 | int i; |
41 | |
42 | for (i = 0; i < n; i++) |
43 | { |
44 | instr[i].need_bufusage = need_buffers; |
45 | instr[i].need_timer = need_timer; |
46 | } |
47 | } |
48 | |
49 | return instr; |
50 | } |
51 | |
52 | /* Initialize a pre-allocated instrumentation structure. */ |
53 | void |
54 | InstrInit(Instrumentation *instr, int instrument_options) |
55 | { |
56 | memset(instr, 0, sizeof(Instrumentation)); |
57 | instr->need_bufusage = (instrument_options & INSTRUMENT_BUFFERS) != 0; |
58 | instr->need_timer = (instrument_options & INSTRUMENT_TIMER) != 0; |
59 | } |
60 | |
61 | /* Entry to a plan node */ |
62 | void |
63 | InstrStartNode(Instrumentation *instr) |
64 | { |
65 | if (instr->need_timer && |
66 | !INSTR_TIME_SET_CURRENT_LAZY(instr->starttime)) |
67 | elog(ERROR, "InstrStartNode called twice in a row" ); |
68 | |
69 | /* save buffer usage totals at node entry, if needed */ |
70 | if (instr->need_bufusage) |
71 | instr->bufusage_start = pgBufferUsage; |
72 | } |
73 | |
74 | /* Exit from a plan node */ |
75 | void |
76 | InstrStopNode(Instrumentation *instr, double nTuples) |
77 | { |
78 | instr_time endtime; |
79 | |
80 | /* count the returned tuples */ |
81 | instr->tuplecount += nTuples; |
82 | |
83 | /* let's update the time only if the timer was requested */ |
84 | if (instr->need_timer) |
85 | { |
86 | if (INSTR_TIME_IS_ZERO(instr->starttime)) |
87 | elog(ERROR, "InstrStopNode called without start" ); |
88 | |
89 | INSTR_TIME_SET_CURRENT(endtime); |
90 | INSTR_TIME_ACCUM_DIFF(instr->counter, endtime, instr->starttime); |
91 | |
92 | INSTR_TIME_SET_ZERO(instr->starttime); |
93 | } |
94 | |
95 | /* Add delta of buffer usage since entry to node's totals */ |
96 | if (instr->need_bufusage) |
97 | BufferUsageAccumDiff(&instr->bufusage, |
98 | &pgBufferUsage, &instr->bufusage_start); |
99 | |
100 | /* Is this the first tuple of this cycle? */ |
101 | if (!instr->running) |
102 | { |
103 | instr->running = true; |
104 | instr->firsttuple = INSTR_TIME_GET_DOUBLE(instr->counter); |
105 | } |
106 | } |
107 | |
108 | /* Finish a run cycle for a plan node */ |
109 | void |
110 | InstrEndLoop(Instrumentation *instr) |
111 | { |
112 | double totaltime; |
113 | |
114 | /* Skip if nothing has happened, or already shut down */ |
115 | if (!instr->running) |
116 | return; |
117 | |
118 | if (!INSTR_TIME_IS_ZERO(instr->starttime)) |
119 | elog(ERROR, "InstrEndLoop called on running node" ); |
120 | |
121 | /* Accumulate per-cycle statistics into totals */ |
122 | totaltime = INSTR_TIME_GET_DOUBLE(instr->counter); |
123 | |
124 | instr->startup += instr->firsttuple; |
125 | instr->total += totaltime; |
126 | instr->ntuples += instr->tuplecount; |
127 | instr->nloops += 1; |
128 | |
129 | /* Reset for next cycle (if any) */ |
130 | instr->running = false; |
131 | INSTR_TIME_SET_ZERO(instr->starttime); |
132 | INSTR_TIME_SET_ZERO(instr->counter); |
133 | instr->firsttuple = 0; |
134 | instr->tuplecount = 0; |
135 | } |
136 | |
137 | /* aggregate instrumentation information */ |
138 | void |
139 | InstrAggNode(Instrumentation *dst, Instrumentation *add) |
140 | { |
141 | if (!dst->running && add->running) |
142 | { |
143 | dst->running = true; |
144 | dst->firsttuple = add->firsttuple; |
145 | } |
146 | else if (dst->running && add->running && dst->firsttuple > add->firsttuple) |
147 | dst->firsttuple = add->firsttuple; |
148 | |
149 | INSTR_TIME_ADD(dst->counter, add->counter); |
150 | |
151 | dst->tuplecount += add->tuplecount; |
152 | dst->startup += add->startup; |
153 | dst->total += add->total; |
154 | dst->ntuples += add->ntuples; |
155 | dst->ntuples2 += add->ntuples2; |
156 | dst->nloops += add->nloops; |
157 | dst->nfiltered1 += add->nfiltered1; |
158 | dst->nfiltered2 += add->nfiltered2; |
159 | |
160 | /* Add delta of buffer usage since entry to node's totals */ |
161 | if (dst->need_bufusage) |
162 | BufferUsageAdd(&dst->bufusage, &add->bufusage); |
163 | } |
164 | |
165 | /* note current values during parallel executor startup */ |
166 | void |
167 | InstrStartParallelQuery(void) |
168 | { |
169 | save_pgBufferUsage = pgBufferUsage; |
170 | } |
171 | |
172 | /* report usage after parallel executor shutdown */ |
173 | void |
174 | InstrEndParallelQuery(BufferUsage *result) |
175 | { |
176 | memset(result, 0, sizeof(BufferUsage)); |
177 | BufferUsageAccumDiff(result, &pgBufferUsage, &save_pgBufferUsage); |
178 | } |
179 | |
180 | /* accumulate work done by workers in leader's stats */ |
181 | void |
182 | InstrAccumParallelQuery(BufferUsage *result) |
183 | { |
184 | BufferUsageAdd(&pgBufferUsage, result); |
185 | } |
186 | |
187 | /* dst += add */ |
188 | static void |
189 | BufferUsageAdd(BufferUsage *dst, const BufferUsage *add) |
190 | { |
191 | dst->shared_blks_hit += add->shared_blks_hit; |
192 | dst->shared_blks_read += add->shared_blks_read; |
193 | dst->shared_blks_dirtied += add->shared_blks_dirtied; |
194 | dst->shared_blks_written += add->shared_blks_written; |
195 | dst->local_blks_hit += add->local_blks_hit; |
196 | dst->local_blks_read += add->local_blks_read; |
197 | dst->local_blks_dirtied += add->local_blks_dirtied; |
198 | dst->local_blks_written += add->local_blks_written; |
199 | dst->temp_blks_read += add->temp_blks_read; |
200 | dst->temp_blks_written += add->temp_blks_written; |
201 | INSTR_TIME_ADD(dst->blk_read_time, add->blk_read_time); |
202 | INSTR_TIME_ADD(dst->blk_write_time, add->blk_write_time); |
203 | } |
204 | |
205 | /* dst += add - sub */ |
206 | static void |
207 | BufferUsageAccumDiff(BufferUsage *dst, |
208 | const BufferUsage *add, |
209 | const BufferUsage *sub) |
210 | { |
211 | dst->shared_blks_hit += add->shared_blks_hit - sub->shared_blks_hit; |
212 | dst->shared_blks_read += add->shared_blks_read - sub->shared_blks_read; |
213 | dst->shared_blks_dirtied += add->shared_blks_dirtied - sub->shared_blks_dirtied; |
214 | dst->shared_blks_written += add->shared_blks_written - sub->shared_blks_written; |
215 | dst->local_blks_hit += add->local_blks_hit - sub->local_blks_hit; |
216 | dst->local_blks_read += add->local_blks_read - sub->local_blks_read; |
217 | dst->local_blks_dirtied += add->local_blks_dirtied - sub->local_blks_dirtied; |
218 | dst->local_blks_written += add->local_blks_written - sub->local_blks_written; |
219 | dst->temp_blks_read += add->temp_blks_read - sub->temp_blks_read; |
220 | dst->temp_blks_written += add->temp_blks_written - sub->temp_blks_written; |
221 | INSTR_TIME_ACCUM_DIFF(dst->blk_read_time, |
222 | add->blk_read_time, sub->blk_read_time); |
223 | INSTR_TIME_ACCUM_DIFF(dst->blk_write_time, |
224 | add->blk_write_time, sub->blk_write_time); |
225 | } |
226 | |