1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 
20 #include "common.h"
21 #include "vmstats.h"
22 #include "log.h"
23 
24 #ifdef _AIX
25 
26 #ifndef XINTFRAC	/* defined in IBM AIX 7.1 libperfstat.h, not defined in AIX 6.1 */
27 #include <sys/systemcfg.h>
28 #define XINTFRAC	((double)_system_configuration.Xint / _system_configuration.Xfrac)
29 	/* Example of XINTFRAC = 125.000000 / 64.000000 = 1.953125. Apparently XINTFRAC is a period (in nanoseconds) */
30 	/* of CPU ticks on a machine. For example, 1.953125 could mean there is 1.953125 nanoseconds between ticks */
31 	/* and number of ticks in second is 1.0 / (1.953125 * 10^-9) = 512000000. So, tick frequency is 512 MHz. */
32 #endif
33 
34 static int		last_clock = 0;
35 /* --- kthr --- */
36 static zbx_uint64_t	last_runque = 0;		/* length of the run queue (processes ready) */
37 static zbx_uint64_t	last_swpque = 0;		/* length of the swap queue (processes waiting to be paged in) */
38 /* --- page --- */
39 static zbx_uint64_t	last_pgins = 0;			/* number of pages paged in */
40 static zbx_uint64_t	last_pgouts = 0;		/* number of pages paged out */
41 static zbx_uint64_t	last_pgspins = 0;		/* number of page ins from paging space */
42 static zbx_uint64_t	last_pgspouts = 0;		/* number of page outs from paging space */
43 static zbx_uint64_t	last_cycles = 0;		/* number of page replacement cycles */
44 static zbx_uint64_t	last_scans = 0;			/* number of page scans by clock */
45 /* -- faults -- */
46 static zbx_uint64_t	last_devintrs = 0;		/* number of device interrupts */
47 static zbx_uint64_t	last_syscall = 0;		/* number of system calls executed */
48 static zbx_uint64_t	last_pswitch = 0;		/* number of process switches (change in currently running */
49 							/* process) */
50 /* --- cpu ---- */
51 /* Raw numbers of ticks are readings from forward-ticking counters. */
52 /* Only difference between 2 readings is meaningful. */
53 static zbx_uint64_t	last_puser = 0;			/* raw number of physical processor ticks in user mode */
54 static zbx_uint64_t	last_psys = 0;			/* raw number of physical processor ticks in system mode */
55 static zbx_uint64_t	last_pidle = 0;			/* raw number of physical processor ticks idle */
56 static zbx_uint64_t	last_pwait = 0;			/* raw number of physical processor ticks waiting for I/O */
57 static zbx_uint64_t	last_user = 0;			/* raw total number of clock ticks spent in user mode */
58 static zbx_uint64_t	last_sys = 0;			/* raw total number of clock ticks spent in system mode */
59 static zbx_uint64_t	last_idle = 0;			/* raw total number of clock ticks spent idle */
60 static zbx_uint64_t	last_wait = 0;			/* raw total number of clock ticks spent waiting for I/O */
61 static zbx_uint64_t	last_timebase_last = 0;		/* most recent processor time base timestamp */
62 static zbx_uint64_t	last_pool_idle_time = 0;	/* number of clock ticks a processor in the shared pool was */
63 							/* idle */
64 static zbx_uint64_t	last_idle_donated_purr = 0;	/* number of idle cycles donated by a dedicated partition */
65 							/* enabled for donation */
66 static zbx_uint64_t	last_busy_donated_purr = 0;	/* number of busy cycles donated by a dedicated partition */
67 							/* enabled for donation */
68 static zbx_uint64_t	last_idle_stolen_purr = 0;	/* number of idle cycles stolen by the hypervisor from */
69 							/* a dedicated partition */
70 static zbx_uint64_t	last_busy_stolen_purr = 0;	/* number of busy cycles stolen by the hypervisor from */
71 							/* a dedicated partition */
72 /* --- disk --- */
73 static zbx_uint64_t	last_xfers = 0;			/* total number of transfers to/from disk */
74 static zbx_uint64_t	last_wblks = 0;			/* 512 bytes blocks written to all disks */
75 static zbx_uint64_t	last_rblks = 0;			/* 512 bytes blocks read from all disks */
76 
77 /******************************************************************************
78  *                                                                            *
79  * Function: update_vmstat                                                    *
80  *                                                                            *
81  * Purpose: update vmstat values at most once per second                      *
82  *                                                                            *
83  * Parameters: vmstat - a structure containing vmstat data                    *
84  *                                                                            *
85  * Comments: on first iteration only save last data, on second - set vmstat   *
86  *           data and indicate that it is available                           *
87  *                                                                            *
88  ******************************************************************************/
update_vmstat(ZBX_VMSTAT_DATA * vmstat)89 static void	update_vmstat(ZBX_VMSTAT_DATA *vmstat)
90 {
91 #if defined(HAVE_LIBPERFSTAT)
92 	int				now;
93 	zbx_uint64_t			dlcpu_us, dlcpu_sy, dlcpu_id, dlcpu_wa, lcputime;
94 	perfstat_memory_total_t		memstats;
95 	perfstat_cpu_total_t		cpustats;
96 	perfstat_disk_total_t		diskstats;
97 #ifdef _AIXVERSION_530
98 	zbx_uint64_t			dpcpu_us, dpcpu_sy, dpcpu_id, dpcpu_wa, pcputime, dtimebase;
99 	zbx_uint64_t			delta_purr, entitled_purr, unused_purr, r1, r2;
100 	perfstat_partition_total_t	lparstats;
101 #ifdef HAVE_AIXOSLEVEL_530006
102 	zbx_uint64_t			didle_donated_purr, dbusy_donated_purr, didle_stolen_purr, dbusy_stolen_purr;
103 #endif	/* HAVE_AIXOSLEVEL_530006 */
104 #endif	/* _AIXVERSION_530 */
105 
106 	now = (int)time(NULL);
107 
108 	/* Retrieve metrics from AIX libperfstat APIs.
109 	 * Upon successful completion, the number of structures filled is returned.
110 	 * If unsuccessful, a value of -1 is returned and the errno global variable is set. */
111 #ifdef _AIXVERSION_530
112 	if (-1 == perfstat_partition_total(NULL, &lparstats, sizeof(lparstats), 1))
113 	{
114 		zabbix_log(LOG_LEVEL_DEBUG, "perfstat_partition_total: %s", zbx_strerror(errno));
115 		return;
116 	}
117 #endif
118 
119 	if (-1 == perfstat_cpu_total(NULL, &cpustats, sizeof(cpustats), 1))
120 	{
121 		zabbix_log(LOG_LEVEL_DEBUG, "perfstat_cpu_total: %s", zbx_strerror(errno));
122 		return;
123 	}
124 
125 	if (-1 == perfstat_memory_total(NULL, &memstats, sizeof(memstats), 1))
126 	{
127 		zabbix_log(LOG_LEVEL_DEBUG, "perfstat_memory_total: %s", zbx_strerror(errno));
128 		return;
129 	}
130 
131 	if (-1 == perfstat_disk_total(NULL, &diskstats, sizeof(diskstats), 1))
132 	{
133 		zabbix_log(LOG_LEVEL_DEBUG, "perfstat_disk_total: %s", zbx_strerror(errno));
134 		return;
135 	}
136 
137 	/* set static vmstat values on first iteration, dynamic on next iterations (at most once per second) */
138 	if (0 == last_clock)
139 	{
140 #ifdef _AIXVERSION_530
141 		vmstat->shared_enabled = (unsigned char)lparstats.type.b.shared_enabled;
142 		vmstat->pool_util_authority = (unsigned char)lparstats.type.b.pool_util_authority;
143 #endif
144 #ifdef HAVE_AIXOSLEVEL_520004
145 		vmstat->aix52stats = 1;
146 #endif
147 	}
148 	else if (now > last_clock)
149 	{
150 		/* --- kthr --- */
151 		vmstat->kthr_r = (double)(cpustats.runque - last_runque) / (now - last_clock);
152 		vmstat->kthr_b = (double)(cpustats.swpque - last_swpque) / (now - last_clock);
153 		/* --- page --- */
154 		vmstat->fi = (double)(memstats.pgins - last_pgins) / (now - last_clock);
155 		vmstat->fo = (double)(memstats.pgouts - last_pgouts) / (now - last_clock);
156 		vmstat->pi = (double)(memstats.pgspins - last_pgspins) / (now - last_clock);
157 		vmstat->po = (double)(memstats.pgspouts - last_pgspouts) / (now - last_clock);
158 		vmstat->fr = (double)(memstats.cycles - last_cycles) / (now - last_clock);
159 		vmstat->sr = (double)(memstats.scans - last_scans) / (now - last_clock);
160 		/* -- faults -- */
161 		vmstat->in = (double)(cpustats.devintrs - last_devintrs) / (now - last_clock);
162 		vmstat->sy = (double)(cpustats.syscall - last_syscall) / (now - last_clock);
163 		vmstat->cs = (double)(cpustats.pswitch - last_pswitch) / (now - last_clock);
164 
165 #ifdef _AIXVERSION_530
166 		/* number of CPU ticks since the last measurement by mode */
167 		dpcpu_us = lparstats.puser - last_puser;
168 		dpcpu_sy = lparstats.psys  - last_psys;
169 		dpcpu_id = lparstats.pidle - last_pidle;
170 		dpcpu_wa = lparstats.pwait - last_pwait;
171 
172 		/* total number of CPU ticks since the last measurement */
173 		delta_purr = dpcpu_us + dpcpu_sy + dpcpu_id + dpcpu_wa;
174 		pcputime = delta_purr;
175 #endif	/* _AIXVERSION_530 */
176 		dlcpu_us = cpustats.user - last_user;
177 		dlcpu_sy = cpustats.sys  - last_sys;
178 		dlcpu_id = cpustats.idle - last_idle;
179 		dlcpu_wa = cpustats.wait - last_wait;
180 
181 		lcputime = dlcpu_us + dlcpu_sy + dlcpu_id + dlcpu_wa;
182 #ifdef _AIXVERSION_530
183 		/* Distribute the donated and stolen purr to the existing purr buckets in case if donation is enabled. */
184 #ifdef HAVE_AIXOSLEVEL_530006
185 		if (lparstats.type.b.donate_enabled)
186 		{
187 			didle_donated_purr = lparstats.idle_donated_purr - last_idle_donated_purr;
188 			dbusy_donated_purr = lparstats.busy_donated_purr - last_busy_donated_purr;
189 
190 			didle_stolen_purr = lparstats.idle_stolen_purr - last_idle_stolen_purr;
191 			dbusy_stolen_purr = lparstats.busy_stolen_purr - last_busy_stolen_purr;
192 
193 			if (0 != dlcpu_id + dlcpu_wa)
194 			{
195 				r1 = dlcpu_id / (dlcpu_id + dlcpu_wa);
196 				r2 = dlcpu_wa / (dlcpu_id + dlcpu_wa);
197 			}
198 			else
199 				r1 = r2 = 0;
200 
201 			dpcpu_us += didle_donated_purr * r1 + didle_stolen_purr * r1;
202 			dpcpu_wa += didle_donated_purr * r2 + didle_stolen_purr * r2;
203 			dpcpu_sy += dbusy_donated_purr + dbusy_stolen_purr;
204 
205 			delta_purr += didle_donated_purr + dbusy_donated_purr + didle_stolen_purr + dbusy_stolen_purr;
206 			pcputime = delta_purr;
207 		}
208 #endif	/* HAVE_AIXOSLEVEL_530006 */
209 
210 		/* number of physical processor tics between current and previous measurement */
211 		dtimebase = lparstats.timebase_last - last_timebase_last;
212 
213 		/* 'perfstat_partition_total_t' element 'entitled_proc_capacity' is "number of processor units this */
214 		/* partition is entitled to receive". It is expressed as multiplied by 100 and rounded to integer, */
215 		/* therefore we divide it by 100 and convert to floating point number to get its real value, as */
216 		/* shown by 'lparstat' command. */
217 		vmstat->ent = lparstats.entitled_proc_capacity / 100.0;
218 
219 		if (lparstats.type.b.shared_enabled)
220 		{
221 			entitled_purr = dtimebase * vmstat->ent;
222 			if (entitled_purr < delta_purr)
223 			{
224 				/* when above entitlement, use consumption in percentages */
225 				entitled_purr = delta_purr;
226 			}
227 			unused_purr = entitled_purr - delta_purr;
228 
229 			/* distribute unused purr in wait and idle proportionally to logical wait and idle */
230 			if (0 != dlcpu_wa + dlcpu_id)
231 			{
232 				dpcpu_wa += unused_purr * ((double)dlcpu_wa / (dlcpu_wa + dlcpu_id));
233 				dpcpu_id += unused_purr * ((double)dlcpu_id / (dlcpu_wa + dlcpu_id));
234 			}
235 
236 			pcputime = entitled_purr;
237 		}
238 
239 		/* Physical Processor Utilization */
240 		vmstat->cpu_us = dpcpu_us * 100.0 / pcputime;
241 		vmstat->cpu_sy = dpcpu_sy * 100.0 / pcputime;
242 		vmstat->cpu_id = dpcpu_id * 100.0 / pcputime;
243 		vmstat->cpu_wa = dpcpu_wa * 100.0 / pcputime;
244 
245 		/* Physical Processor Consumed */
246 		/* Interesting values only for "shared" LPARs. */
247 		/* For "dedicated" LPARs expect approximately the same value as assigned to the LPAR through HMC. */
248 		vmstat->cpu_pc = (double)delta_purr / dtimebase;
249 
250 		if (lparstats.type.b.shared_enabled)
251 		{
252 			/* Percentage of Entitlement Consumed */
253 			vmstat->cpu_ec = (vmstat->cpu_pc / vmstat->ent) * 100.0;
254 
255 			/* Logical Processor Utilization */
256 			vmstat->cpu_lbusy = (dlcpu_us + dlcpu_sy) * 100.0 / lcputime;
257 
258 			if (lparstats.type.b.pool_util_authority)
259 			{
260 				/* Available Pool Processor (app) */
261 				vmstat->cpu_app = (lparstats.pool_idle_time - last_pool_idle_time) /
262 						(XINTFRAC * dtimebase);
263 			}
264 		}
265 		else
266 			vmstat->cpu_ec = 100.0;		/* trivial value for LPAR type "dedicated" */
267 
268 #else	/* not _AIXVERSION_530 */
269 
270 		/* Physical Processor Utilization */
271 		vmstat->cpu_us = dlcpu_us * 100.0 / lcputime;
272 		vmstat->cpu_sy = dlcpu_sy * 100.0 / lcputime;
273 		vmstat->cpu_id = dlcpu_id * 100.0 / lcputime;
274 		vmstat->cpu_wa = dlcpu_wa * 100.0 / lcputime;
275 
276 #endif	/* _AIXVERSION_530 */
277 		/* --- disk --- */
278 		vmstat->disk_bps = 512 * ((diskstats.wblks - last_wblks) + (diskstats.rblks - last_rblks)) / (now - last_clock);
279 		vmstat->disk_tps = (double)(diskstats.xfers - last_xfers) / (now - last_clock);
280 
281 		/* -- memory -- */
282 #ifdef HAVE_AIXOSLEVEL_520004
283 		vmstat->mem_avm = (zbx_uint64_t)memstats.virt_active;	/* Active virtual pages. Virtual pages are considered
284 									   active if they have been accessed */
285 #endif
286 		vmstat->mem_fre = (zbx_uint64_t)memstats.real_free;	/* free real memory (in 4KB pages) */
287 
288 		/* indicate that vmstat data is available */
289 		vmstat->data_available = 1;
290 	}
291 
292 	/* saving last values */
293 	last_clock = now;
294 	/* --- kthr -- */
295 	last_runque = (zbx_uint64_t)cpustats.runque;
296 	last_swpque = (zbx_uint64_t)cpustats.swpque;
297 	/* --- page --- */
298 	last_pgins = (zbx_uint64_t)memstats.pgins;
299 	last_pgouts = (zbx_uint64_t)memstats.pgouts;
300 	last_pgspins = (zbx_uint64_t)memstats.pgspins;
301 	last_pgspouts = (zbx_uint64_t)memstats.pgspouts;
302 	last_cycles = (zbx_uint64_t)memstats.cycles;
303 	last_scans = (zbx_uint64_t)memstats.scans;
304 	/* -- faults -- */
305 	last_devintrs = (zbx_uint64_t)cpustats.devintrs;
306 	last_syscall = (zbx_uint64_t)cpustats.syscall;
307 	last_pswitch = (zbx_uint64_t)cpustats.pswitch;
308 	/* --- cpu ---- */
309 #ifdef _AIXVERSION_530
310 	last_puser = (zbx_uint64_t)lparstats.puser;
311 	last_psys = (zbx_uint64_t)lparstats.psys;
312 	last_pidle = (zbx_uint64_t)lparstats.pidle;
313 	last_pwait = (zbx_uint64_t)lparstats.pwait;
314 
315 	last_timebase_last = (zbx_uint64_t)lparstats.timebase_last;
316 
317 	last_pool_idle_time = (zbx_uint64_t)lparstats.pool_idle_time;
318 
319 #ifdef HAVE_AIXOSLEVEL_530006
320 	last_idle_donated_purr = (zbx_uint64_t)lparstats.idle_donated_purr;
321 	last_busy_donated_purr = (zbx_uint64_t)lparstats.busy_donated_purr;
322 
323 	last_idle_stolen_purr = (zbx_uint64_t)lparstats.idle_stolen_purr;
324 	last_busy_stolen_purr = (zbx_uint64_t)lparstats.busy_stolen_purr;
325 #endif	/* HAVE_AIXOSLEVEL_530006 */
326 #endif	/* _AIXVERSION_530 */
327 	last_user = (zbx_uint64_t)cpustats.user;
328 	last_sys = (zbx_uint64_t)cpustats.sys;
329 	last_idle = (zbx_uint64_t)cpustats.idle;
330 	last_wait = (zbx_uint64_t)cpustats.wait;
331 
332 	last_xfers = (zbx_uint64_t)diskstats.xfers;
333 	last_wblks = (zbx_uint64_t)diskstats.wblks;
334 	last_rblks = (zbx_uint64_t)diskstats.rblks;
335 #endif	/* HAVE_LIBPERFSTAT */
336 }
337 
collect_vmstat_data(ZBX_VMSTAT_DATA * vmstat)338 void	collect_vmstat_data(ZBX_VMSTAT_DATA *vmstat)
339 {
340 	update_vmstat(vmstat);
341 }
342 
343 #endif	/* _AIX */
344